mobile: sync with latest core changes

This commit is contained in:
Ammar Ahmed
2023-12-27 09:40:15 +05:00
committed by Abdullah Atta
parent 0fcab638a3
commit c2d11cdc79
23 changed files with 127 additions and 149 deletions

View File

@@ -46,15 +46,15 @@ export const FileDownloadStatus = {
* Download all user's attachments
* @returns
*/
export function downloadAllAttachments() {
const attachments = db.attachments.all;
export async function downloadAllAttachments() {
const attachments = await db.attachments.all.ids();
return downloadAttachments(attachments);
}
/**
* Downloads provided attachments to a .zip file
* on user's device.
* @param attachments
* @param {string[]} attachments
* @param onProgress
* @returns
*/
@@ -89,8 +89,8 @@ export async function downloadAttachments(
await RNFetchBlob.fs.mkdir(zipSourceFolder);
for (let i = 0; i < attachments.length; i++) {
let attachment = attachments[i];
const hash = attachment.metadata.hash;
let attachment = await db.attachments.attachment(attachments[i]);
const hash = attachment.hash;
try {
if (canceled.current) {
RNFetchBlob.fs.unlink(zipSourceFolder).catch(console.log);
@@ -114,15 +114,16 @@ export async function downloadAttachments(
}
if (!uri) throw new Error("Failed to download file");
// Move file to the source folder we will zip eventually and rename the file to it's actual name.
const filePath = `${zipSourceFolder}/${attachment.metadata.filename}`;
const filePath = `${zipSourceFolder}/${attachment.filename}`;
await RNFetchBlob.fs.mv(`${cacheDir}/${uri}`, filePath);
result.set(hash, {
filename: attachment.metadata.filename,
status: FileDownloadStatus.Success
filename: attachment.filename,
status: FileDownloadStatus.Success,
attachment: attachment
});
} catch (e) {
result.set(hash, {
filename: attachment.metadata.filename,
filename: attachment.filename,
status: FileDownloadStatus.Fail,
reason: e
});
@@ -208,14 +209,12 @@ export default async function downloadAttachment(
try {
console.log(
"starting download attachment",
attachment.metadata.hash,
attachment.hash,
options.groupId
);
await db.fs.downloadFile(
options.groupId || attachment.metadata.hash,
attachment.metadata.hash
);
await db
.fs()
.downloadFile(options.groupId || attachment.hash, attachment.hash);
if (!(await exists(attachment.metadata.hash))) {
return;
}
@@ -228,19 +227,19 @@ export default async function downloadAttachment(
}
let filename = getFileNameWithExtension(
attachment.metadata.filename,
attachment.metadata.type
attachment.filename,
attachment.mimeType
);
let key = await db.attachments.decryptKey(attachment.key);
let info = {
iv: attachment.iv,
salt: attachment.salt,
length: attachment.length,
length: attachment.size,
alg: attachment.alg,
hash: attachment.metadata.hash,
hashType: attachment.metadata.hashType,
mime: attachment.metadata.type,
hash: attachment.hash,
hashType: attachment.hashType,
mime: attachment.mimeType,
fileName: options.cache ? undefined : filename,
uri: options.cache ? undefined : folder.uri,
chunkSize: attachment.chunkSize,
@@ -263,11 +262,11 @@ export default async function downloadAttachment(
if (
attachment.dateUploaded &&
!isImage(attachment.metadata?.type) &&
!isDocument(attachment.metadata?.type)
!isImage(attachment.mimeType) &&
!isDocument(attachment.mimeType)
) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
}
if (Platform.OS === "ios" && !options.cache) {
@@ -282,7 +281,7 @@ export default async function downloadAttachment(
: "File Manager/Notesnook/downloads"
}`,
icon: "download",
context: global ? null : attachment.metadata.hash,
context: global ? null : attachment.hash,
component: <ShareComponent uri={fileUri} name={filename} padding={12} />
});
}
@@ -292,10 +291,13 @@ export default async function downloadAttachment(
console.log("download attachment error: ", e);
if (attachment.dateUploaded) {
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.metadata.hash}`)
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}`)
.catch(console.log);
RNFetchBlob.fs
.unlink(RNFetchBlob.fs.dirs.CacheDir + `/${attachment.hash}_dcache`)
.catch(console.log);
}
useAttachmentStore.getState().remove(attachment.metadata.hash);
useAttachmentStore.getState().remove(attachment.hash);
if (options.throwError) {
throw e;
}

View File

@@ -119,7 +119,7 @@ export async function checkAttachment(hash) {
const isInternetReachable =
internetState.isConnected && internetState.isInternetReachable;
if (!isInternetReachable) return { success: true };
const attachment = db.attachments.attachment(hash);
const attachment = await db.attachments.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {

View File

@@ -35,14 +35,10 @@ export async function readEncrypted(filename, key, cipherData) {
return false;
}
const attachment = db.attachments.attachment(filename);
const isPng = !attachment.metadata.type
? false
: /(png)/g.test(attachment?.metadata.type);
const isJpeg = !attachment.metadata.type
? false
: /(jpeg|jpg)/g.test(attachment?.metadata.type);
const attachment = await db.attachments.attachment(filename);
const isPng = /(png)/g.test(attachment?.mimeType || "");
const isJpeg = /(jpeg|jpg)/g.test(attachment?.mimeType || "");
console.log("decrypting....");
let output = await Sodium.decryptFile(
key,
{
@@ -194,10 +190,10 @@ export async function exists(filename) {
}
if (exists || existsInAppGroup) {
const attachment = db.attachments.attachment(filename);
const totalChunks = Math.ceil(attachment.length / attachment.chunkSize);
const attachment = await db.attachments.attachment(filename);
const totalChunks = Math.ceil(attachment.size / attachment.chunkSize);
const totalAbytes = totalChunks * ABYTES;
const expectedFileSize = attachment.length + totalAbytes;
const expectedFileSize = attachment.size + totalAbytes;
const stat = await RNFetchBlob.fs.stat(
existsInAppGroup ? appGroupPath : path

View File

@@ -87,12 +87,9 @@ export async function uploadFile(filename, data, cancelToken) {
DatabaseLogger.info(
`File upload status: ${filename}, ${status}, ${text}`
);
let attachment = db.attachments.attachment(filename);
let attachment = await db.attachments.attachment(filename);
if (!attachment) return result;
if (
!isImage(attachment.metadata.type) &&
!isDocument(attachment.metadata?.type)
) {
if (!isImage(attachment.mimeType) && !isDocument(attachment.mimeType)) {
RNFetchBlob.fs.unlink(`${cacheDir}/${filename}`).catch(console.log);
}
} else {

View File

@@ -63,7 +63,7 @@ const DownloadAttachments = ({
canceled.current = false;
groupId.current = Date.now().toString();
const result = await downloadAttachments(
attachments,
await attachments.ids(),
(progress: number, statusText: string) =>
setProgress({ value: progress, statusText }),
canceled,
@@ -89,29 +89,21 @@ const DownloadAttachments = ({
groupId.current = undefined;
};
const successResults = () => {
const results = [];
for (const [key, value] of result.entries()) {
if (value.status === 1) results.push(db.attachments.attachment(key));
}
return results;
};
const failedResults = () => {
const results = [];
for (const [key, value] of result.entries()) {
if (value.status === 0) results.push(db.attachments.attachment(key));
for (const value of result.values()) {
if (value.status === 0) results.push(value.attachment);
}
return results;
};
function getResultText() {
const downloadedAttachmentsCount =
attachments?.ids?.length - failedResults().length;
attachments?.placeholders?.length - failedResults().length;
if (downloadedAttachmentsCount === 0)
return "Failed to download all attachments";
return `Successfully downloaded ${downloadedAttachmentsCount}/${
attachments?.ids.length
attachments?.placeholders.length
} attachments as a zip file at ${
Platform.OS === "android" ? "the selected folder" : "Notesnook/downloads"
}`;
@@ -174,7 +166,9 @@ const DownloadAttachments = ({
animated={true}
useNativeDriver
progress={
progress.value ? progress.value / attachments.ids?.length : 0
progress.value
? progress.value / attachments.placeholders?.length
: 0
}
unfilledColor={colors.secondary.background}
color={colors.primary.accent}
@@ -192,7 +186,7 @@ const DownloadAttachments = ({
borderRadius: 5,
marginVertical: 12
}}
data={downloading ? attachments.ids : undefined}
data={downloading ? attachments.placeholders : undefined}
ListEmptyComponent={
<View
style={{
@@ -207,7 +201,7 @@ const DownloadAttachments = ({
</Paragraph>
</View>
}
keyExtractor={(item, index) => "attachment" + index}
keyExtractor={(index) => "attachment" + index}
renderItem={({ index }) => {
return (
<AttachmentItem

View File

@@ -92,13 +92,13 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
}, 300);
};
const renderItem = ({ item }: { item: string | number }) => (
const renderItem = ({ index }: { item: boolean; index: number }) => (
<AttachmentItem
setAttachments={() => {
setAttachments(filterAttachments(currentFilter));
}}
attachments={attachments}
id={item}
id={index}
context="attachments-list"
/>
);
@@ -106,10 +106,10 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
const onCheck = async () => {
if (!attachments) return;
setLoading(true);
for (const id of attachments.ids) {
const attachment = (await attachments.item(id))?.item;
if (!attachment) continue;
for (let i = 0; i < attachments.placeholders.length; i++) {
const attachment = (await attachments.item(i))?.item;
if (!attachment) continue;
const result = await filesystem.checkAttachment(attachment.hash);
if (result.failed) {
await db.attachments.markAsFailed(attachment.hash, result.failed);
@@ -310,7 +310,7 @@ export const AttachmentDialog = ({ note }: { note?: Note }) => {
/>
}
estimatedItemSize={50}
data={attachments?.ids}
data={attachments?.placeholders}
renderItem={renderItem}
/>

View File

@@ -17,7 +17,7 @@ You should have received a copy of the GNU General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
import React, { useEffect, useRef, useState } from "react";
import React, { useCallback, useEffect, useRef, useState } from "react";
import { Dimensions, TextInput, View } from "react-native";
import {
addOrientationListener,
@@ -85,7 +85,7 @@ const PDFPreview = () => {
return () => {
eUnSubscribeEvent("PDFPreview", open);
};
}, []);
}, [open]);
const onOrientationChange = (o) => {
if (o.includes("LANDSCAPE")) {
@@ -102,23 +102,26 @@ const PDFPreview = () => {
};
}, []);
const open = async (attachment) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
setAttachment(attachment);
let hash = attachment.metadata.hash;
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
snapshotValue.current = snapshot.current;
setPDFSource("file://" + path);
setLoading(false);
}, 100);
};
const open = useCallback(
async (attachment) => {
setVisible(true);
setLoading(true);
setTimeout(async () => {
setAttachment(attachment);
let hash = attachment.hash;
if (!hash) return;
const uri = await downloadAttachment(hash, false, {
silent: true,
cache: true
});
const path = `${cacheDir}/${uri}`;
snapshotValue.current = snapshot.current;
setPDFSource("file://" + path);
setLoading(false);
}, 100);
},
[snapshot]
);
const close = () => {
setPDFSource(null);
@@ -130,7 +133,7 @@ const PDFPreview = () => {
if (error?.message === "Password required or incorrect password.") {
await sleep(300);
presentDialog({
context: attachment?.metadata?.hash,
context: attachment?.hash,
input: true,
inputPlaceholder: "Enter password",
positiveText: "Unlock",
@@ -151,8 +154,8 @@ const PDFPreview = () => {
return (
visible && (
<BaseDialog animation="fade" visible={true} onRequestClose={close}>
<SheetProvider context={attachment?.metadata?.hash} />
<Dialog context={attachment?.metadata?.hash} />
<SheetProvider context={attachment?.hash} />
<Dialog context={attachment?.hash} />
<View
style={{
@@ -261,7 +264,7 @@ const PDFPreview = () => {
color={colors.static.white}
name="download"
onPress={() => {
downloadAttachment(attachment.metadata.hash, false);
downloadAttachment(attachment.hash, false);
}}
/>
</View>

View File

@@ -155,7 +155,7 @@ export default function List(props: ListProps) {
style={styles}
ref={scrollRef}
testID={notesnook.list.id}
data={props.data?.ids || []}
data={props.data?.placeholders || []}
renderItem={renderItem}
onScroll={onListScroll}
nestedScrollEnabled={true}

View File

@@ -19,13 +19,14 @@ along with this program. If not, see <http://www.gnu.org/licenses/>.
import { Item, ItemType, VirtualizedGrouping } from "@notesnook/core";
import { useThemeColors } from "@notesnook/theme";
import React, { useCallback, useEffect } from "react";
import React, { useEffect } from "react";
import {
BackHandler,
NativeEventSubscription,
Platform,
View
} from "react-native";
import Animated, { FadeInUp } from "react-native-reanimated";
import { db } from "../../common/database";
import useGlobalSafeAreaInsets from "../../hooks/use-global-safe-area-insets";
import { ToastManager } from "../../services/event-manager";
@@ -43,7 +44,6 @@ import ExportNotesSheet from "../sheets/export-notes";
import ManageTagsSheet from "../sheets/manage-tags";
import { IconButton } from "../ui/icon-button";
import Heading from "../ui/typography/heading";
import Animated, { FadeInUp } from "react-native-reanimated";
export const SelectionHeader = React.memo(
({
@@ -65,8 +65,7 @@ export const SelectionHeader = React.memo(
const clearSelection = useSelectionStore((state) => state.clearSelection);
const insets = useGlobalSafeAreaInsets();
const allSelected =
items?.ids?.filter((id) => typeof id === "string").length ===
selectedItemsList.length;
items?.placeholders?.length === selectedItemsList.length;
const focusedRouteId = useNavigationStore((state) => state.focusedRouteId);
useEffect(() => {
@@ -200,15 +199,7 @@ export const SelectionHeader = React.memo(
onPress={async () => {
useSelectionStore
.getState()
.setAll(
allSelected
? []
: [
...((items?.ids.filter(
(id) => typeof id !== "object"
) as string[]) || [])
]
);
.setAll(allSelected ? [] : [...((await items?.ids()) || [])]);
}}
tooltipText="Select all"
tooltipPosition={4}

View File

@@ -31,14 +31,12 @@ import { db } from "../../../common/database";
import { presentSheet } from "../../../services/event-manager";
import Navigation from "../../../services/navigation";
import { ItemSelection } from "../../../stores/item-selection-store";
import {
useNotebookStore,
useNotebooks
} from "../../../stores/use-notebook-store";
import { useNotebooks } from "../../../stores/use-notebook-store";
import { useRelationStore } from "../../../stores/use-relation-store";
import { useSelectionStore } from "../../../stores/use-selection-store";
import { useSettingStore } from "../../../stores/use-setting-store";
import { updateNotebook } from "../../../utils/notebooks";
import { SIZE } from "../../../utils/size";
import { Dialog } from "../../dialog";
import DialogHeader from "../../dialog/dialog-header";
import SheetProvider from "../../sheet-provider";
@@ -47,7 +45,6 @@ import { IconButton } from "../../ui/icon-button";
import Paragraph from "../../ui/typography/paragraph";
import { NotebookItem } from "./notebook-item";
import { useNotebookItemSelectionStore } from "./store";
import { SIZE } from "../../../utils/size";
async function updateInitialSelectionState(items: string[]) {
const relations = await db.relations
@@ -144,7 +141,7 @@ const MoveNoteSheet = ({
};
const renderNotebook = useCallback(
({ item, index }: { item: string | number; index: number }) => (
({ index }: { item: boolean; index: number }) => (
<NotebookItem items={notebooks} id={index} index={index} />
),
[notebooks]
@@ -228,7 +225,7 @@ const MoveNoteSheet = ({
}}
>
<FlashList
data={notebooks?.ids}
data={notebooks?.placeholders}
style={{
width: "100%"
}}

View File

@@ -146,7 +146,7 @@ export const NotebookItem = ({
alignItems: "center"
}}
>
{nestedNotebooks?.ids.length ? (
{nestedNotebooks?.placeholders.length ? (
<IconButton
size={SIZE.xl}
color={isSelected ? colors.selected.icon : colors.primary.icon}
@@ -240,7 +240,7 @@ export const NotebookItem = ({
{!expanded
? null
: nestedNotebooks?.ids.map((id, index) => (
: nestedNotebooks?.placeholders.map((id, index) => (
<NotebookItem
key={item?.id + "_" + index}
id={index}

View File

@@ -115,7 +115,7 @@ const ManageTagsSheet = (props: {
});
} else {
db.tags.all.sorted(db.settings.getGroupOptions("tags")).then((items) => {
console.log("items loaded tags", items.ids);
console.log("items loaded tags", items.placeholders.length);
setTags(items);
});
}
@@ -237,7 +237,7 @@ const ManageTagsSheet = (props: {
);
const renderTag = useCallback(
({ item, index }: { item: string | number; index: number }) => (
({ index }: { item: boolean; index: number }) => (
<TagItem
tags={tags as VirtualizedGrouping<Tag>}
id={index}
@@ -302,7 +302,7 @@ const ManageTagsSheet = (props: {
) : null}
<FlatList
data={tags?.ids}
data={tags?.placeholders}
style={{
width: "100%"
}}

View File

@@ -88,7 +88,7 @@ export const MoveNotes = ({
);
const renderItem = React.useCallback(
({ index }: { item: string | number; index: number }) => {
({ index }: { item: boolean; index: number }) => {
return (
<SelectableNoteItem
id={index}
@@ -132,7 +132,7 @@ export const MoveNotes = ({
</Paragraph>
</View>
}
data={notes?.ids}
data={notes?.placeholders}
renderItem={renderItem}
/>
{selectedNoteIds.length > 0 ? (

View File

@@ -140,13 +140,7 @@ export const NotebookSheet = () => {
loading: "Loading notebook topics"
};
const renderNotebook = ({
item,
index
}: {
item: string | number;
index: number;
}) => (
const renderNotebook = ({ index }: { item: boolean; index: number }) => (
<NotebookItem
items={notebooks}
id={index}
@@ -403,7 +397,7 @@ export const NotebookSheet = () => {
</View>
<SelectionContext.Provider value={selectionContext}>
<FlashList
data={notebooks?.ids}
data={notebooks?.placeholders}
style={{
width: "100%"
}}
@@ -508,7 +502,7 @@ const NotebookItem = ({
alignItems: "center"
}}
>
{nestedNotebooks?.ids.length ? (
{nestedNotebooks?.placeholders.length ? (
<IconButton
size={SIZE.lg}
color={isSelected ? colors.selected.icon : colors.primary.icon}
@@ -601,7 +595,7 @@ const NotebookItem = ({
{!expanded
? null
: item &&
nestedNotebooks?.ids.map((id, index) => (
nestedNotebooks?.placeholders.map((id, index) => (
<NotebookItem
key={item.id + "_" + index}
id={index}

View File

@@ -76,7 +76,7 @@ export const RelationsList = ({
const [items, setItems] = useState<VirtualizedGrouping<Item>>();
const hasNoRelations = !items || items?.ids?.length === 0;
const hasNoRelations = !items || items?.placeholders?.length === 0;
useEffect(() => {
db.relations?.[relationType]?.(
@@ -85,13 +85,12 @@ export const RelationsList = ({
)
.selector.sorted({
sortBy: "dateEdited",
sortDirection: "desc",
groupBy: "default"
sortDirection: "desc"
})
.then((grouped) => {
setItems(grouped);
});
}, [relationType, referenceType]);
}, [relationType, referenceType, item?.id, item?.type]);
return (
<View

View File

@@ -150,13 +150,13 @@ export default function ReminderNotify({
})}
</ScrollView>
{references?.ids && references?.ids?.length > 0 ? (
{references?.placeholders && references?.placeholders?.length > 0 ? (
<View
style={{
width: "100%",
height:
160 * references?.ids?.length < 500
? 160 * references?.ids?.length
160 * references?.placeholders?.length < 500
? 160 * references?.placeholders?.length
: 500,
borderTopWidth: 1,
borderTopColor: colors.secondary.background,

View File

@@ -235,7 +235,7 @@ const NotebookScreen = ({ route, navigation }: NavigationProps<"Notebook">) => {
AddNotebookSheet.present(params.current.item);
}}
notebook={params.current.item}
totalNotes={notes?.ids.length || 0}
totalNotes={notes?.placeholders.length || 0}
/>
}
placeholder={{

View File

@@ -56,8 +56,8 @@ export const Notebooks = ({
});
useEffect(() => {
if (notebooks?.ids) {
if (notebooks?.ids?.length === 0 && !Config.isTesting) {
if (notebooks?.placeholders) {
if (notebooks?.placeholders?.length === 0 && !Config.isTesting) {
Walkthrough.present("notebooks");
} else {
Walkthrough.update("notebooks");
@@ -99,7 +99,9 @@ export const Notebooks = ({
headerTitle="Notebooks"
/>
{!notebooks || notebooks.ids.length === 0 || !isFocused ? null : (
{!notebooks ||
notebooks.placeholders.length === 0 ||
!isFocused ? null : (
<FloatingButton
title="Create a new notebook"
onPress={onButtonPress}

View File

@@ -126,7 +126,7 @@ const NotesPage = ({
true
)) as VirtualizedGrouping<Note>;
if (notes.ids.length === 0) setLoadingNotes(false);
if (notes.placeholders.length === 0) setLoadingNotes(false);
setNotes(notes);
await notes.item(0, resolveItems);
setLoadingNotes(false);
@@ -204,7 +204,8 @@ const NotesPage = ({
/>
{!isMonograph &&
((notes?.ids && (notes?.ids?.length || 0) > 0) || isFocused) ? (
((notes?.placeholders && (notes?.placeholders?.length || 0) > 0) ||
isFocused) ? (
<FloatingButton
color={accentColor}
title="Create a note"

View File

@@ -65,9 +65,11 @@ export const Search = ({ route, navigation }: NavigationProps<"Search">) => {
query,
route.params.items as FilteredSelector<Note>
).sorted();
console.log(`Found ${results.ids?.length} results for ${query}`);
console.log(
`Found ${results.placeholders?.length} results for ${query}`
);
setResults(results);
if (results.ids?.length === 0) {
if (results.placeholders?.length === 0) {
setSearchStatus(`No results found for ${query}`);
} else {
setSearchStatus(undefined);

View File

@@ -107,7 +107,7 @@ export const Trash = ({ navigation, route }: NavigationProps<"Trash">) => {
headerTitle="Trash"
/>
{trash && trash?.ids?.length !== 0 ? (
{trash && trash?.placeholders?.length !== 0 ? (
<FloatingButton
title="Clear all trash"
onPress={onPressFloatingButton}

View File

@@ -173,7 +173,7 @@ const NotebookItem = ({
alignItems: "center"
}}
>
{nestedNotebooks?.ids.length ? (
{nestedNotebooks?.placeholders.length ? (
<TouchableOpacity
style={{
width: 35,
@@ -234,14 +234,14 @@ const NotebookItem = ({
</View>
</View>
{nestedNotebooks?.ids?.length && isExpanded ? (
{nestedNotebooks?.placeholders?.length && isExpanded ? (
<View
style={{
paddingLeft: level + 1 > 0 && level + 1 < 5 ? 15 : 0,
marginTop: 5
}}
>
{nestedNotebooks.ids.map((item, index) => (
{nestedNotebooks.placeholders.map((item, index) => (
<NotebookItem
key={notebook?.id + index}
id={index}
@@ -396,7 +396,7 @@ export const Search = ({
}, [get]);
const renderItem = React.useCallback(
({ index }: { item: string | number; index: number }) =>
({ index }: { item: boolean; index: number }) =>
mode === "selectNotebooks" ? (
<NotebookItem
id={index}
@@ -509,7 +509,7 @@ export const Search = ({
) : null}
<FlatList
data={items?.ids}
data={items?.placeholders}
keyboardShouldPersistTaps="always"
keyboardDismissMode="none"
renderItem={renderItem}

View File

@@ -89,7 +89,7 @@ export async function downloadAttachment<
}
export async function checkAttachment(hash: string) {
const attachment = db.attachments.attachment(hash);
const attachment = await db.attachments.attachment(hash);
if (!attachment) return { failed: "Attachment not found." };
try {