File size: 4,854 Bytes
5dc4327
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
import urllib.parse
import re
from fastapi import APIRouter, Request, HTTPException, Header
from fastapi.responses import StreamingResponse, Response
from mfinder.db.files_sql import SESSION, Files

router = APIRouter()

def get_file_by_name(file_name: str) -> Files:
    """Safely retrieves file details from the SQL database without session leaking."""
    try:
        session = SESSION()
        return session.query(Files).filter_by(file_name=file_name).first()
    except Exception as e:
        print(f"Error querying file details for download: {e}")
        return None
    finally:
        try:
            SESSION.close()
        except Exception:
            pass

@router.get("/dl/{file_name}")
async def download_file(request: Request, file_name: str, range: str = Header(None)):
    """

    Asynchronously streams a Telegram file directly to the browser chunk-by-chunk.

    Supports HTTP Range requests (206 Partial Content) for accelerated multi-thread downloads (IDM/ADM/Chrome).

    Uses a dedicated stream_client to keep main bot commands 100% fast and responsive for 50+ concurrent users.

    """
    decoded_file_name = urllib.parse.unquote(file_name)
    
    file_details = get_file_by_name(decoded_file_name)
    if not file_details:
        raise HTTPException(status_code=404, detail="File not found in database.")
    
    file_id = file_details.file_id
    file_size = int(file_details.file_size)
    mime_type = file_details.mime_type or "application/octet-stream"
    
    # Use dedicated stream_client if available to isolate file streaming from bot commands
    stream_client = getattr(request.app.state, "stream_client", None) or getattr(request.app.state, "client", None)
    if not stream_client:
        raise HTTPException(status_code=503, detail="Telegram client gateway not initialized.")
        
    start_bytes = 0
    end_bytes = file_size - 1

    if range:
        range_match = re.match(r"bytes=(\d+)-(\d+)?", range)
        if range_match:
            start_bytes = int(range_match.group(1))
            if range_match.group(2):
                end_bytes = int(range_match.group(2))

    if start_bytes >= file_size or end_bytes >= file_size or start_bytes > end_bytes:
        headers = {"Content-Range": f"bytes */{file_size}"}
        return Response(status_code=416, headers=headers)

    content_length = (end_bytes - start_bytes) + 1

    chunk_offset = start_bytes // (1024 * 1024)
    skip_bytes = start_bytes % (1024 * 1024)

    async def media_generator():
        import asyncio
        queue = asyncio.Queue(maxsize=3)
        
        async def producer():
            try:
                async for chunk in stream_client.stream_media(file_id, offset=chunk_offset):
                    await queue.put(chunk)
                await queue.put(None)
            except Exception as e:
                print(f"Error in producer: {e}")
                await queue.put(None)
                
        producer_task = asyncio.create_task(producer())
        bytes_sent = 0
        try:
            first = True
            while True:
                chunk = await queue.get()
                if chunk is None:
                    break
                    
                if first:
                    first = False
                    if skip_bytes > 0:
                        chunk = chunk[skip_bytes:]
                        
                if bytes_sent + len(chunk) > content_length:
                    chunk = chunk[:content_length - bytes_sent]
                    
                if not chunk:
                    break
                    
                yield chunk
                bytes_sent += len(chunk)
                
                if bytes_sent >= content_length:
                    break
        except Exception as e:
            print(f"Error occurred while streaming file '{decoded_file_name}': {e}")
        finally:
            producer_task.cancel()
            try:
                await producer_task
            except asyncio.CancelledError:
                pass
            except Exception as ex:
                print(f"Error clean cancelling producer: {ex}")
            
    headers = {
        "Content-Disposition": f"attachment; filename=\"{decoded_file_name}\"; filename*=UTF-8''{urllib.parse.quote(decoded_file_name)}",
        "Content-Length": str(content_length),
        "Accept-Ranges": "bytes"
    }

    if range:
        headers["Content-Range"] = f"bytes {start_bytes}-{end_bytes}/{file_size}"
        return StreamingResponse(media_generator(), status_code=206, media_type=mime_type, headers=headers)
    else:
        return StreamingResponse(media_generator(), status_code=200, media_type=mime_type, headers=headers)