Skip to content
This repository was archived by the owner on Apr 23, 2024. It is now read-only.

Commit 5a13ca2

Browse files
authored
Merge pull request #451 from mgilangjanuar/staging
Staging
2 parents 3ac21be + 4ee54e1 commit 5a13ca2

8 files changed

Lines changed: 139 additions & 87 deletions

File tree

api/src/api/v1/Files.ts

Lines changed: 22 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -307,17 +307,19 @@ export class Files {
307307
const files = await prisma.files.findMany({
308308
where: {
309309
AND: [
310-
{
311-
name: {
312-
startsWith: body.name.replace(/\.part0*\d+$/, '')
313-
}
314-
},
315-
{
316-
user_id: req.user?.id
317-
},
318-
{
319-
parent_id: source.parent_id
320-
},
310+
{ name: source.name.endsWith('.part001') ? { startsWith: source.name.replace(/\.part0*\d+$/, '.part') } : source.name },
311+
{ user_id: req.user?.id },
312+
{ parent_id: source.parent_id },
313+
]
314+
}
315+
})
316+
317+
const countExists = await prisma.files.count({
318+
where: {
319+
AND: [
320+
{ name: source.name.endsWith('.part001') ? { startsWith: source.name.replace(/\.part0*\d+$/, ''), endsWith: '.part001' } : { startsWith: source.name } },
321+
{ user_id: req.user?.id },
322+
{ parent_id: body.parent_id }
321323
]
322324
}
323325
})
@@ -328,8 +330,8 @@ export class Files {
328330
const { forward_info: forwardInfo, message_id: messageId, mime_type: mimeType } = file
329331
let peerFrom: Api.InputPeerChannel | Api.InputPeerUser | Api.InputPeerChat
330332
let peerTo: Api.InputPeerChannel | Api.InputPeerUser | Api.InputPeerChat
331-
const [type, peerId, _id, accessHash] = forwardInfo?.split('/') ?? []
332333
if (forwardInfo && forwardInfo.match(/^channel\//gi)) {
334+
const [type, peerId, _id, accessHash] = forwardInfo?.split('/') ?? []
333335
if (type === 'channel') {
334336
peerFrom = new Api.InputPeerChannel({
335337
channelId: bigInt(peerId),
@@ -343,8 +345,8 @@ export class Files {
343345
chatId: bigInt(peerId) })
344346
}
345347
}
348+
const [type, peerId, _, accessHash] = ((req.user.settings as Prisma.JsonObject).saved_location as string).split('/')
346349
if ((req.user.settings as Prisma.JsonObject)?.saved_location) {
347-
const [type, peerId, _, accessHash] = ((req.user.settings as Prisma.JsonObject).saved_location as string).split('/')
348350
if (type === 'channel') {
349351
peerTo = new Api.InputPeerChannel({
350352
channelId: bigInt(peerId),
@@ -368,7 +370,7 @@ export class Files {
368370
dropAuthor: true
369371
})) as any
370372

371-
const newForwardInfo = forwardInfo ? `${type}/${peerId}/${chat.updates[0].id.toString()}/${accessHash}` : null
373+
const newForwardInfo = peerTo ? `${type}/${peerId}/${chat.updates[0].id.toString()}/${accessHash}` : null
372374
const message = {
373375
size: Number(file.size),
374376
message_id: chat.updates[0].id.toString(),
@@ -380,7 +382,7 @@ export class Files {
380382
const response = await prisma.files.create({
381383
data: {
382384
...body,
383-
name: files.length == 1 ? body.name : body.name.replace(/\.part0*\d+$/, '')+`.part${String(countFiles + 1).padStart(3, '0')}`,
385+
name: files.length == 1 ? body.name + `${countExists ? ` (${countExists})` : ''}` : body.name.replace(/\.part0*\d+$/, '')+`${countExists ? ` (${countExists})` : ''}`+`.part${String(countFiles + 1).padStart(3, '0')}`,
384386
...message
385387
}
386388
})
@@ -1065,15 +1067,16 @@ export class Files {
10651067

10661068
const getSizes = ({ size, sizes }) => sizes ? sizes.pop() : size
10671069
const size = file.media.photo ? getSizes(file.media.photo.sizes.pop()) : file.media.document?.size
1068-
let type = file.media.photo || mimeType.match(/^image/gi) ? 'image' : null
1070+
let type = file.media.photo
10691071
if (file.media.document?.mimeType.match(/^video/gi) || name.match(/\.mp4$/gi) || name.match(/\.mkv$/gi) || name.match(/\.mov$/gi)) {
10701072
type = 'video'
10711073
} else if (file.media.document?.mimeType.match(/pdf$/gi) || name.match(/\.doc$/gi) || name.match(/\.docx$/gi) || name.match(/\.xls$/gi) || name.match(/\.xlsx$/gi)) {
10721074
type = 'document'
10731075
} else if (file.media.document?.mimeType.match(/audio$/gi) || name.match(/\.mp3$/gi) || name.match(/\.ogg$/gi)) {
10741076
type = 'audio'
1077+
} else if (file.media.document?.mimeType.match(/^image/gi) || name.match(/\.jpg$/gi) || name.match(/\.jpeg$/gi) || name.match(/\.png$/gi) || name.match(/\.gif$/gi)) {
1078+
type = 'image'
10751079
}
1076-
10771080
return {
10781081
name,
10791082
message_id: file.id.toString(),
@@ -1200,7 +1203,7 @@ export class Files {
12001203
const end = ranges[1] ? ranges[1] : totalFileSize.toJSNumber() - 1
12011204

12021205
const readStream = createReadStream(filename(), { start, end })
1203-
res.writeHead(206, {
1206+
res.writeHead(200, {
12041207
'Cache-Control': 'public, max-age=604800',
12051208
'ETag': Buffer.from(`${files[0].id}:${files[0].message_id}`).toString('base64'),
12061209
'Content-Range': `bytes ${start}-${end}/${totalFileSize}`,
@@ -1211,7 +1214,7 @@ export class Files {
12111214
})
12121215
readStream.pipe(res)
12131216
} else {
1214-
res.writeHead(206, {
1217+
res.writeHead(200, {
12151218
'Cache-Control': 'public, max-age=604800',
12161219
'ETag': Buffer.from(`${files[0].id}:${files[0].message_id}`).toString('base64'),
12171220
'Content-Range': `bytes */${totalFileSize}`,

docs/docs/Installation/manual.md

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,10 @@ If it's succeed you don't need to follow the steps below.
5151
npm i -g yarn
5252
```
5353

54-
- Define all api variables in `./api/.env`, you can copy from `./api/.env.example`
54+
55+
- Define all
56+
variables in `./api/.env`, you can copy from `./api/.env.example`
57+
5558

5659
```shell
5760
cp ./api/.env.example ./api/.env
@@ -124,7 +127,10 @@ yarn workspaces run build
124127
## Run:
125128

126129
```shell
130+
127131
yarn api prisma migrate deploy
132+
133+
128134
cd api && node dist/index.js
129135
```
130136

@@ -139,9 +145,8 @@ git pull origin main # or, staging for the latest updates
139145

140146
yarn install # install
141147
yarn workspaces run build # build
142-
143-
yarn api prisma migrate deploy
148+
yarn api prisma migrate deploy
144149
cd api && node dist/index.js # run
145150
```
146151

147-
Next, you can deploy TeleDrive with [Vercel](/docs/deployment/vercel) or [PM2](/docs/deployment/pm2).
152+
Next, you can deploy TeleDrive with [Vercel](/docs/deployment/vercel) or [PM2](/docs/deployment/pm2).

install.caprover.sh

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#!/bin/bash
2+
3+
set -e
4+
5+
echo "Node Version: $(node -v)"
6+
echo "Yarn Version: $(yarn -v)"
7+
8+
if [ ! -f docker/.env ]
9+
then
10+
echo "Generating docker/.env file..."
11+
12+
ENV="develop"
13+
14+
echo "Preparing your keys from https://my.telegram.org/"
15+
read -p "Enter your TG_API_ID: " TG_API_ID
16+
read -p "Enter your TG_API_HASH: " TG_API_HASH
17+
18+
echo "ENV=$ENV" > docker/.env
19+
echo "TG_API_ID=$TG_API_ID" >> docker/.env
20+
echo "TG_API_HASH=$TG_API_HASH" >> docker/.env
21+
fi
22+
23+
git reset --hard
24+
git clean -f
25+
git pull origin main
26+
27+
export $(cat docker/.env | xargs)
28+
29+
echo
30+
echo "Build and deploy to CapRover..."
31+
docker build --build-arg REACT_APP_TG_API_ID=$TG_API_ID --build-arg REACT_APP_TG_API_HASH=$TG_API_HASH -t myapp .
32+
caprover deploy --appName myapp --imageName myapp

package.json

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
{
22
"name": "teledrive",
3-
"version": "2.5.2",
3+
"version": "2.5.4",
44
"repository": "git@github.com:mgilangjanuar/teledrive.git",
55
"author": "M Gilang Januar <mgilangjanuar@teledriveapp.com>",
66
"license": "MIT",
77
"private": true,
8+
"engines": {
9+
"node": "16.14.0"
10+
},
11+
812
"scripts": {
913
"web": "yarn workspace web",
1014
"server": "yarn workspace api",

web/src/pages/dashboard/components/Rename.tsx

Lines changed: 13 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,32 +25,40 @@ const Rename: React.FC<Props> = ({
2525

2626
const renameFile = async () => {
2727
setLoadingRename(true)
28-
const { name } = formRename.getFieldsValue()
28+
const name = String(formRename.getFieldsValue().name)
2929
try {
30+
const { data: exists } = await req.get('/files', { params: { parent_id: fileRename.parent_id, name: name } })
31+
if (/\.part0*\d*$/.test(name))
32+
throw { status: 400, body: { error: 'The file name cannot end with ".part", even if followed by digits!' } }
33+
if (/\(\d+\).+/.test(name))
34+
throw { status: 400, body: { error: 'The file name cannot contain text after parentheses with digits inside!' } }
35+
if (exists.length > 0)
36+
throw { status: 400, body: { error: `A file/folder named "${name}" already exists!` } }
37+
3038
const { data: result } = await req.patch(`/files/${fileRename?.id}`, {
3139
file: { name }
3240
})
3341
notification.success({
3442
message: 'Success',
35-
description: `${name} renamed successfully!`
43+
description: `${fileRename?.name.replace(/\.part0*\d+$/, '')} renamed successfully!`
3644
})
3745
dataSource?.[1](dataSource?.[0].map((datum: any) => datum.id === result.file.id ? { ...datum, name } : datum))
3846
setFileRename(undefined)
3947
setLoadingRename(false)
4048
formRename.resetFields()
4149
onFinish?.()
42-
} catch (error) {
50+
} catch (error: any) {
4351
setLoadingRename(false)
4452
return notification.error({
4553
message: 'Error',
46-
description: 'Failed to rename a file. Please try again!'
54+
description: error?.body?.error || 'Failed to rename a file. Please try again!'
4755
})
4856
}
4957
}
5058

5159
return <Modal visible={fileRename}
5260
onCancel={() => setFileRename(undefined)}
53-
okText="Add"
61+
okText="Rename"
5462
title={<Typography.Text ellipsis>Rename {fileRename?.name.replace(/\.part0*\d+$/, '')}</Typography.Text>}
5563
onOk={() => formRename.submit()}
5664
cancelButtonProps={{ shape: 'round' }}

web/src/pages/dashboard/components/Upload.tsx

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,14 @@ const Upload: React.FC<Props> = ({ dataFileList: [fileList, setFileList], parent
6969
let deleted = false
7070

7171
try {
72+
const { data: exists } = await req.get('/files', { params: { parent_id: parent?.id, name: file.name } })
73+
if (/\.part0*\d*$/.test(file.name))
74+
throw { status: 400, body: { error: 'The file name cannot end with ".part", even if followed by digits!' } }
75+
if (/\(\d+\).+/.test(file.name))
76+
throw { status: 400, body: { error: 'The file name cannot contain text after parentheses with digits inside!' } }
77+
if (exists.length > 0)
78+
throw { status: 400, body: { error: `A file/folder named "${file.name}" already exists!` } }
79+
7280
while (filesWantToUpload.current?.findIndex(f => f.uid === file.uid) !== 0) {
7381
await new Promise(res => setTimeout(res, 1000))
7482
}

web/src/pages/dashboard/index.tsx

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,7 @@ const Dashboard: React.FC<PageProps & { me?: any, errorMe?: any }> = ({ match })
272272
const name = `Link of ${row.name}`
273273
await req.post('/files/addFolder', { file: { ...row, name, link_id: row.id, parent_id: p?.link_id || p?.id, id: undefined } })
274274
} else {
275-
const name = data?.find(datum => datum.name === row.name) ? `Copy of ${row.name}` : row.name
276-
await req.post('/files/cloneFile', { file: { ...row, name, parent_id: p?.link_id || p?.id, id: undefined } })
275+
await req.post('/files/cloneFile', { file: { ...row, name: row.name, parent_id: p?.link_id || p?.id, id: undefined } })
277276
}
278277
}))
279278
} else if ((act || action) === 'cut') {

web/src/utils/Download.ts

Lines changed: 49 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -2,77 +2,70 @@ import streamSaver from 'streamsaver'
22
import { Api } from 'telegram'
33
import { req } from './Fetcher'
44
import { telegramClient } from './Telegram'
5-
5+
async function downloadFile(client, file) {
6+
let chat
7+
if (file.forward_info && file.forward_info.match(/^channel\//gi)) {
8+
const [type, peerId, id, accessHash] = file.forward_info.split('/')
9+
let peer: Api.InputPeerChannel | Api.InputPeerUser | Api.InputPeerChat
10+
if (type === 'channel') {
11+
peer = new Api.InputPeerChannel({
12+
channelId: BigInt(peerId) as any,
13+
accessHash: BigInt(accessHash as string) as any,
14+
})
15+
chat = await client.invoke(
16+
new Api.channels.GetMessages({
17+
channel: peer,
18+
id: [new Api.InputMessageID({ id: Number(id) })],
19+
})
20+
)
21+
}
22+
} else {
23+
chat = await client.invoke(
24+
new Api.messages.GetMessages({
25+
id: [new Api.InputMessageID({ id: Number(file.message_id) })],
26+
})
27+
)
28+
}
29+
return client.downloadMedia(chat['messages'][0].media, {
30+
outputFile: {
31+
write: (chunk: Buffer) => {
32+
return true
33+
},
34+
},
35+
})
36+
}
637
export async function download(id: string): Promise<ReadableStream> {
7-
const { data: response } = await req.get(`/files/${id}`, { params: { raw: 1, as_array: 1 } })
8-
let cancel = false
38+
const { data: response } = await req.get(`/files/${id}`, {
39+
params: { raw: 1, as_array: 1 },
40+
})
941
const client = await telegramClient.connect()
42+
const filesToDownload = response.files
1043
const readableStream = new ReadableStream({
11-
start(_controller: ReadableStreamDefaultController) {
12-
},
13-
async pull(controller: ReadableStreamDefaultController) {
14-
let countFiles = 1
15-
console.log('start downloading:', response.files)
16-
for (const file of response.files) {
17-
let chat: any
18-
if (file.forward_info && file.forward_info.match(/^channel\//gi)) {
19-
const [type, peerId, id, accessHash] = file.forward_info.split('/')
20-
let peer: Api.InputPeerChannel | Api.InputPeerUser | Api.InputPeerChat
21-
if (type === 'channel') {
22-
peer = new Api.InputPeerChannel({
23-
channelId: BigInt(peerId) as any,
24-
accessHash: BigInt(accessHash as string) as any
25-
})
26-
chat = await client.invoke(new Api.channels.GetMessages({
27-
channel: peer,
28-
id: [new Api.InputMessageID({ id: Number(id) })]
29-
}))
30-
}
31-
} else {
32-
chat = await client.invoke(new Api.messages.GetMessages({
33-
id: [new Api.InputMessageID({ id: Number(file.message_id) })]
34-
}))
35-
}
36-
const getData = async () => await client.downloadMedia(chat['messages'][0].media, {
37-
outputFile: {
38-
write: (chunk: Buffer) => {
39-
if (cancel) return false
40-
return controller.enqueue(chunk)
41-
},
42-
close: () => {
43-
if (countFiles++ >= Number(response.files.length)) controller.close()
44-
}
45-
},
46-
progressCallback: (received, total) => {
47-
console.log('progress: ', (Number(received) / Number(total) * 100).toFixed(2), '%')
48-
}
49-
})
44+
async pull(controller) {
45+
for (const [index, file] of filesToDownload.entries()) {
5046
try {
51-
await getData()
47+
const data = await downloadFile(client, file)
48+
controller.enqueue(data)
49+
if (index === filesToDownload.length - 1) {
50+
controller.close()
51+
}
5252
} catch (error) {
53-
console.log(error)
53+
console.error(error)
5454
}
5555
}
5656
},
57-
cancel() {
58-
cancel = true
59-
}
60-
}, {
61-
size(chunk: any) {
62-
return chunk.length
63-
}
6457
})
6558
return readableStream
6659
}
67-
6860
export const directDownload = async (id: string, name: string): Promise<void> => {
6961
const fileStream = streamSaver.createWriteStream(name)
7062
const writer = fileStream.getWriter()
7163
const reader = (await download(id)).getReader()
72-
const pump = () => reader.read().then(({ value, done }) => {
64+
const pump = async () => {
65+
const { value, done } = await reader.read()
7366
if (done) return writer.close()
74-
writer.write(value)
75-
return writer.ready.then(pump)
76-
})
67+
await writer.write(value)
68+
await pump()
69+
}
7770
await pump()
7871
}

0 commit comments

Comments
 (0)