Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add samba support #72

Draft
wants to merge 13 commits into
base: main
Choose a base branch
from
3 changes: 3 additions & 0 deletions apps/web/hooks/useBucket.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { DriveFile, DriveFolder, Provider, Tag, UploadingFile } from "@util/type
import useFirebase from "./useFirebase";
import useKeys from "./useKeys";
import useS3 from "./useS3";
import useSamba from "./useSamba";
import useS3Shared from "./sharedBuckets/useS3Shared";

export type ContextValue = {
Expand Down Expand Up @@ -44,6 +45,8 @@ export default function useBucket(): ContextValue {
case Provider.cloudflare:
case Provider.scaleway:
return keys.permissions === "owned" ? useS3() : useS3Shared();
case Provider.samba:
return useSamba();
default:
return null;
}
Expand Down
152 changes: 152 additions & 0 deletions apps/web/hooks/useSamba.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
import { createContext, PropsWithChildren, useContext, useEffect, useRef, useState } from "react";
import SambaClient from "samba-client";
import { ContextValue, ROOT_FOLDER } from "./useBucket";
import { Drive } from "@prisma/client";
import { DriveFile, DriveFolder, UploadingFile } from "@util/types";
import toast from "react-hot-toast";
import useUser from "./useUser";
import mime from "mime-types";

const SambaContext = createContext<ContextValue>(null);
export default () => useContext(SambaContext);

type Props = {
data: Drive & { keys: any };
fullPath?: string;
};
export const SambaProvider: React.FC<PropsWithChildren<Props>> = ({ data, fullPath, children }) => {
const sambaClient = new SambaClient({
address: data.keys.address,
username: "" || data.keys.username,
password: "" || data.keys.password,
domain: "WORKGROUP" || data.keys.domain,
maxProtocol: "SMB3",
})
const { user } = useUser();
const [loading, setLoading] = useState(false);
const [currentFolder, setCurrentFolder] = useState<DriveFolder>(null);
const [folders, setFolders] = useState<DriveFolder[]>(null);
const [uploadingFiles, setUploadingFiles] = useState<UploadingFile[]>([]);
const [files, setFiles] = useState<DriveFile[]>(null);
const isMounted = useRef(false);
const enableTags = false;

const addFolder = async (name: string) => {
const path =
currentFolder.fullPath !== ""
? decodeURIComponent(currentFolder.fullPath) + name + "/"
: name + "/";
const newFolder: DriveFolder = {
name,
fullPath: path,
parent: currentFolder.fullPath
}
setFolders((folders) => [...folders, newFolder])
await sambaClient.mkdir(newFolder.name, path);
const localFolders = localStorage.getItem(`local_folders_${data.id}`);
const folders: DriveFolder[] = localFolders ? JSON.parse(localFolders) : [];
localStorage.setItem(`local_folders_${data.id}`, JSON.stringify([...folders, newFolder]));
};

const removeFolder = async (folder: DriveFolder) => {
setFolders((folders) => folders.filter((f) => f.fullPath !== folder.fullPath));
const localFolders = localStorage.getItem(`local_folders_${data.id}`);
if (localFolders) {
const folders = JSON.parse(localFolders);
const filtered = folders.filter((f) => !f.fullPath.includes(folder.fullPath));
localStorage.setItem(`local_folders_${data.id}`, JSON.stringify(filtered));
}
await sambaClient.deleteFile(folder.fullPath)
}

const addFile = async (filesToUpload: File[] | FileList) => {
Array.from(filesToUpload).forEach(async (file) => {
if (/[#\$\[\]\*/]/.test(file.name))
return toast.error("File name cannot contain special characters (#$[]*/).");

const Key =
currentFolder === ROOT_FOLDER
? file.name
: `${decodeURIComponent(currentFolder.fullPath)}${file.name}`;
if (await sambaClient.fileExists(file.name, Key)) {
return toast.error("File with same name already exists.");
}
await sambaClient.sendFile(file.name, Key)
})
}

const removeFile = async (file: DriveFile) => {
setFiles((files) => files.filter((f) => f.fullPath !== file.fullPath));
await sambaClient.deleteFile(file.fullPath);
return true;
}

useEffect(() => {
if (!user?.email) return;
setFiles(null);
setFolders(null);

if (fullPath === "" || !fullPath) {
setCurrentFolder(ROOT_FOLDER);
return;
}

setCurrentFolder({
fullPath: fullPath + "/",
name: fullPath.split("/").pop(),
parent: fullPath.split("/").shift() + "/",
});
}, [fullPath, user]);

// get files and folders
useEffect(() => {
if (!user?.email || !currentFolder) return;
setLoading(true);

(async () => {
try {
if (!files) {
var results = await sambaClient.list(currentFolder.fullPath)
results.forEach(async (result) => {
const driveFile: DriveFile = {
fullPath: currentFolder.fullPath + "/" + result.name,
name: result.name.split("/").pop(),
parent: currentFolder.fullPath,
createdAt: result.modifyTime.toISOString(),
size: result.size.toString(),
contentType: mime.lookup(result.type) || "",
};

setFiles((files) => (files ? [...files, driveFile] : [driveFile]));
});

const localFolders = localStorage.getItem(`local_folders_${data.id}`);
setLoading(false);
}
}
catch (err) {
console.error(err);
}
})
}, [currentFolder, user]);

return (
<SambaContext.Provider
value={{
loading,
currentFolder,
files,
folders,
uploadingFiles,
enableTags,
setUploadingFiles,
addFile,
addFolder,
removeFile,
removeFolder,
}}
>
{children}
</SambaContext.Provider>
);
};
1 change: 1 addition & 0 deletions apps/web/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"react-loading-overlay": "^1.0.1",
"resend": "^0.17.1",
"sendgrid": "^5.2.3",
"samba-client": "^7.2.0",
"swr": "^1.1.2-beta.0",
"tabler-icons-react": "^1.45.0",
"underscore": "^1.13.2",
Expand Down
5 changes: 5 additions & 0 deletions apps/web/pages/drives/[id].tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Dashboard from "@components/Dashboard";
import { FirebaseProvider } from "@hooks/useFirebase";
import { KeysProvider } from "@hooks/useKeys";
import { S3Provider } from "@hooks/useS3";
import { SambaProvider } from "@hooks/useSamba";
import { S3SharedProvider } from "@hooks/sharedBuckets/useS3Shared";
import useUser from "@hooks/useUser";
import { Drive, Role } from "@prisma/client";
Expand Down Expand Up @@ -58,6 +59,10 @@ const DrivePage: React.FC<Props> = ({ data, role }) => {
<Dashboard />
</S3SharedProvider>
)
) : data.type === "samba" ? (
<SambaProvider data={data} fullPath={decodeURIComponent(folderPath)}>
<Dashboard />
</SambaProvider>
) : (
<p>No provider found.</p>
)}
Expand Down
Loading