TrendShift 딥다이브 · 2026-07-18 · Daily #12

XiaoYouChR/Ghost-Downloader-3 딥다이브
— IDM식 멀티스레드 다운로더를 Python·Qt로 다시 쓴 오픈소스

Ghost Downloader 3(줄여서 GD3)은 HTTP·토렌트·FTP·M3U8·유튜브·비리비리까지 한 프로그램에서 받아내는, IDM(Internet Download Manager) 스타일의 "쪼개서 동시에 받는" 다운로더다. 상용 IDM을 Python + Qt로 다시 만든 오픈소스판이라고 보면 가깝다. 화면은 마이크로소프트의 Fluent Design 룩을 따르고, 엔진 하나로 Windows·macOS·Linux·Android 네 플랫폼에서 똑같이 돈다. 가장 먼저 각인할 것 — 이 저장소가 트렌딩에 오른 진짜 이유는 "또 하나의 다운로더"라서가 아니라, 파일을 여러 조각으로 나눠 받되 "다 받고 나서 합치는" 과정을 없앤 무병합(no-merge) 설계와, 속도를 실시간으로 재서 조각 수를 스스로 늘리는 "AI 스마트 가속" 때문이다. 그리고 코드베이스에는 아키텍처·용어·명명 규칙을 32KB 문서 하나(CONTEXT.md)로 정리해 둔, 보기 드문 "설계 교과서"가 딸려 있다. 이 문서는 소스를 직접 클론해 "이 저장소에서 무엇을 배울 수 있는가"를 파고든다. (저장소: XiaoYouChR/Ghost-Downloader-3 · Python 3.11+ / PySide6(Qt) · GPL v3.0 · 별 약 6.5k · 포크 약 359 · 최신 릴리스 v4.1.1(2026-07-15) · 최신 커밋 2026-07-16 · 저자 XiaoYouChR · TrendShift Daily #12)
목차
  1. 프로젝트 한 줄 요약
  2. 왜 지금 주목받는가
  3. 기술 스택 전체 지도
  4. 아키텍처 심화 분석 — 액터와 무병합 다운로드
  5. 디렉토리 구조 해부
  6. 학습 포인트 — 기술별 배울 것
  7. 시스템 · 실행 요구사항
  8. 직접 해볼 수 있는 실습 과제
  9. 관련 기술 심화 학습 로드맵
  10. 핵심 키워드 사전
  11. 참고 링크

1프로젝트 한 줄 요약

이 저장소가 정확히 무엇인가

Ghost Downloader 3"파일을 여러 조각으로 나눠 동시에 받아 속도를 끌어올리고, HTTP뿐 아니라 토렌트·FTP·M3U8·유튜브·비리비리까지 한 창에서 처리하는 크로스플랫폼 다운로드 관리자"다. 저자 XiaoYouChR가 "내 첫 Python 프로젝트"라고 README에 적어둔, 취미로 시작해 별 6.5천 개까지 자란 프로그램이다.

한 문장 비유

"한 명이 물통 하나로 물을 나르는 대신, 여덟 명이 동시에 각자 다른 층계참에서 물을 부어 채우는 저수조."

보통 브라우저 다운로드는 서버와 연결 하나로 처음부터 끝까지 받는다. GD3는 파일을 8조각으로 나눠 "바이트 3천만~4천만 번은 네가, 4천만~5천만 번은 네가" 하고 8개의 연결에 동시에 시킨다. 각 일꾼은 자기 담당 구간을 파일의 정확한 그 위치에 직접 써넣기 때문에, 나중에 조각들을 이어 붙이는 "병합" 단계가 아예 없다.

이름의 숫자 "3"은 3세대라는 뜻이다. 초기 버전들은 순수 다운로드 기능에 가까웠고, 지금의 3세대(내부 버전 4.x)는 플러그인 구조·브라우저 확장·안드로이드 앱·aria2 호환 API까지 갖춘 하나의 플랫폼으로 커졌다. 저장소를 클론해 보면 크게 네 덩어리로 나뉜다.

구성물정체규모
app/플랫폼 공용 "엔진" — 서비스(액터) 계층 + 데이터 모델 + Qt 화면(View)핵심 로직
features/프로토콜별 플러그인 12종 (http·bittorrent·ftp·m3u8·ed2k·bili·yt_dlp·github·huggingface·ffmpeg·disk 등)기능 확장
browser_extension/브라우저 확장 (TypeScript/React) — 웹페이지 미디어를 낚아채 앱으로 보냄별도 서브프로젝트
android/안드로이드 패키징 (같은 Python 엔진을 모바일 껍데기에 얹음)모바일 포트

Python·Qt 소스만 약 22만 줄(라이브러리 포함) 규모이고, 라이선스는 GPL v3.0이다. 무료·완전 오픈소스이며 상용 IDM을 대체하려는 성격이 뚜렷하다.

용어
다운로드 관리자 (Download Manager)
단순히 파일을 받기만 하는 게 아니라 일시정지·재개, 여러 조각 병렬 수신, 속도 제한, 목록 관리를 해주는 프로그램. 대표 상용 제품이 IDM(Internet Download Manager)이며, GD3는 그 무료 오픈소스 대안을 지향한다.

2왜 지금 주목받는가

경쟁 제품이 즐비한 "다운로더" 시장에서 이 저장소가 트렌딩에 오른 이유

다운로더는 흔한 소프트웨어다. 그런데도 GD3가 주목받는 건 흔한 기능을 흔하지 않게 구현했기 때문이다. 세 가지가 핵심이다.

① 무병합(no-merge) 분할 — "받고 나서 합치기"를 없앴다

대부분의 멀티스레드 다운로더는 조각을 file.part1, file.part2처럼 따로 받아뒀다가 마지막에 하나로 이어 붙인다(병합). 큰 파일이면 이 병합만으로도 디스크를 통째로 한 번 더 읽고 쓴다. GD3는 받는 즉시 각 조각을 최종 파일의 정확한 오프셋(offset)에 직접 써넣어 병합 단계를 통째로 없앴다. 리눅스/맥의 pwrite(위치 지정 쓰기)를 그대로 쓰기 때문에 가능한 설계다.

비유

퍼즐을 맞출 때, 조각을 다 모아뒀다가 마지막에 한꺼번에 판에 올리는 방식(병합)과, 조각이 손에 들어오는 즉시 제자리에 바로 끼우는 방식(무병합)의 차이다. 후자는 "다 모으는 시간"과 "따로 쌓아둘 공간"이 필요 없다.

② AI 스마트 가속 — 조각 수를 실시간으로 스스로 조절

"AI"라는 단어에 거창한 신경망을 떠올리기 쉽지만, 여기서는 속도를 초당 재면서 "조각을 더 쪼개면 실제로 빨라지는지"를 통계적으로 판단하는 자동 튜닝 루프를 말한다. 서버가 연결당 속도를 제한하는 경우(대역폭 throttling)에는 조각을 늘릴수록 총속도가 오른다. GD3는 5초간 속도를 지켜본 뒤 조각을 4개 추가로 쪼개고, "조각을 N% 늘렸는데 속도가 그만큼(0.8배 이상) 안 올랐다"면 더는 이득이 없다고 보고 가속을 멈춘다. 뒤(4장)에서 실제 코드를 뜯어본다.

③ TLS 지문 위장 — 봇 차단을 피하는 다운로더

요즘 서버는 접속자가 "진짜 크롬 브라우저"인지 "스크립트/봇"인지를 TLS 지문(fingerprint)으로 구분해 차단한다. GD3는 wreq 라이브러리로 실제 크롬·파이어폭스의 TLS 악수(handshake) 지문을 흉내 내, 다운로더가 보내는 요청을 브라우저 요청처럼 보이게 만든다. 이건 일반 다운로더에는 거의 없는 기능이다.

여기에 브라우저 확장으로 스트리밍 미디어를 낚아채고, aria2 호환 RPC로 외부 도구가 작업을 밀어 넣게 하고, 같은 엔진으로 안드로이드까지 지원하는 넓은 커버리지가 더해진다. "기능이 많다"가 아니라 "각 기능을 제대로 만들었다"가 트렌딩의 실질적 이유다.

배울 점
흔한 도메인일수록 "구현의 질"이 차별점이 된다

다운로더는 수십 개가 있지만, 무병합 쓰기·자동 가속 휴리스틱·TLS 위장·플러그인 아키텍처를 한 곳에서 깔끔하게 볼 수 있는 오픈소스는 드물다. 신입 개발자에게 "잘 설계된 실전 앱"의 표본으로 특히 값지다.

3기술 스택 전체 지도

엔진 · UI · 프로토콜 · 확장 — 각 층에서 무엇을 쓰는가

GD3는 "GUI 앱"이지만 속을 보면 비동기 네트워크 엔진 + Qt 화면 + 12개의 프로토콜 플러그인이 겹쳐진 구조다. 계층별로 어떤 도구를 쓰는지 지도를 그려보자.

런타임 · 언어 기반

요소선택왜 이걸 썼나
언어Python 3.11+접근성이 높아 향후 플러그인 생태계를 열기 쉬움(저자 명시)
패키지 관리uv + pyproject.tomlRust 기반 초고속 의존성 해결기. uv.lock으로 재현성 확보
비동기 루프asyncio + uvloop(리눅스/맥) / winloop(윈도우)C로 짠 초고속 이벤트 루프로 수백 개 동시 연결 감당
배포 컴파일NuitkaPython을 C로 변환·컴파일해 단일 실행파일로 배포(속도·보호)

UI 계층 (화면)

요소선택역할
GUI 프레임워크PySide6 (Qt 6.6+)Qt의 공식 파이썬 바인딩. 크로스플랫폼 네이티브 위젯
디자인 위젯PyQt-Fluent-WidgetsWindows 11 Fluent Design 스타일 컴포넌트 라이브러리
이벤트 브리지Signal / Slot + Coroutine Runnerasyncio(백그라운드 스레드)와 Qt(메인 스레드)를 안전하게 연결
용어
Qt · PySide6
Qt는 C++로 만든 대표적인 크로스플랫폼 GUI 툴킷이고, PySide6은 그 Qt를 파이썬에서 쓰게 해주는 공식 바인딩이다. "시그널(어떤 일이 일어났다)"과 "슬롯(그 일에 반응하는 함수)"으로 화면과 로직을 연결하는 게 Qt의 핵심 방식이다.

네트워크 · 프로토콜

프로토콜사용 라이브러리메모
HTTP/HTTPSwreq (TLS 지문 위장)브라우저 위장 + 바이트 레인지 병렬 수신의 핵심
Magnet / BTlibtorrent (C++ 바인딩)비트토렌트 세션·트래커 처리
FTP/FTPSaioftp (+SOCKS)비동기 FTP 클라이언트
M3U8 / MPEG-DASHm3u8, mpegdash, N_m3u8DL-REHLS/DASH 스트리밍·라이브 녹화·실시간 복호화
eD2kgoed2k (내장 서브모듈)eDonkey 네트워크 지원
YouTube / Bilibiliyt-dlp + 자체 파서재생목록·4K/HDR·자막·로그인 지원
미디어 처리FFmpeg, QuickJS-NG변환·병합, JS 실행(사이트 스크립트 파싱)

주변 시스템

요소선택역할
브라우저 확장TypeScript + React (MV3 서비스워커)웹페이지 미디어 스니핑, WebSocket으로 앱과 통신
외부 APIaria2 호환 JSON-RPC 서버 (Qt TCP)서드파티 도구가 작업을 직접 밀어 넣게 함
로깅loguru간결한 파이썬 로깅
알림desktop-notifier크로스플랫폼 데스크톱 알림
번역Crowdin다국어 커뮤니티 번역(1.4만 단어+)
한눈에 보는 비유

GD3는 "엔진 룸(app/services) + 계기판(PySide6 UI) + 교체 가능한 어댑터 소켓(features/*_pack)"으로 된 자동차다. 엔진(비동기 다운로드 루프)은 한 대뿐인데, 여기에 HTTP용·토렌트용·유튜브용 어댑터를 소켓에 꽂으면 그 프로토콜을 받을 수 있게 된다. 계기판은 데스크톱/모바일 두 종류로 갈아 끼운다.

4아키텍처 심화 분석 — 액터와 무병합 다운로드

이 저장소의 진짜 배울거리: 서비스 액터 구조 + 조각 병렬 수신의 실제 코드

GD3의 아키텍처는 CONTEXT.md에 아주 정밀하게 문서화돼 있다. 핵심은 두 가지 축이다 — (A) "액터(actor)"로 나눈 서비스 계층(B) HTTP 조각 병렬 수신 엔진. 순서대로 뜯어본다.

4-1. 전체 시스템 구조도

┌──────────────────────────────────────────────────────────────┐ │ View / Entry 계층 (Qt 위젯 — 사용자 의도 수집 + 화면 렌더) │ │ MainWindow · TaskPage(가상 스크롤) · TaskDraftDialog │ └───────────────┬──────────────────────────────────────────────┘ │ 시그널로만 연결 (서비스는 View를 모름) ▼ ┌──────────────────────────────────────────────────────────────┐ │ Service 계층 (플랫폼 무관 "액터" = 모듈 싱글턴) │ │ │ │ taskService ◄── 유일한 공개 창구 (add/pause/delete/stop) │ │ ├ TaskStore (Task 객체 + tasks.jsonl 영속화) [내부 전용] │ │ └ TaskQueue (동시 실행 슬롯 제한 + 취소 핸들) [내부 전용] │ │ │ │ featureService ── 팩 발견·파서 우선순위 라우팅 │ │ speedMeter ────── 바이트 누적·초당 속도·속도제한 게이트 │ │ coroutineRunner ─ 백그라운드 asyncio 루프 (Task를 전혀 모름) │ │ categoryService · clipboardListener · plan · aria2RpcServer │ └───────────────┬──────────────────────────────────────────────┘ │ featureService.parse(options) → Task ▼ ┌──────────────────────────────────────────────────────────────┐ │ FeaturePack 계층 (features/*_pack — 프로토콜 플러그인) │ │ HttpParser(100) · TorrentParser(85) · YouTubeParser(70) … │ │ 각 팩이 Task Parser·Card·Runtime·Page·Settings를 기여 │ └──────────────────────────────────────────────────────────────┘

이 그림에서 반드시 챙길 두 규칙:

① 단일 공개 창구(single public door). 화면이든 브라우저든 작업을 만들거나 멈추려면 무조건 taskService를 거친다. 작업 목록(TaskStore)과 실행 큐(TaskQueue)는 taskService 안쪽에 숨겨져 있어 아무도 직접 못 만진다. "누가 상태를 바꿀 수 있는가"를 딱 한 곳으로 좁힌 설계다.

② 의존성은 한 방향(leaf → top)으로만 흐른다. coroutineRunnerspeedMeter는 "Task"가 뭔지조차 모른다. 그저 "비동기 작업을 돌려라", "바이트를 세라"는 일반 기능만 한다. 반대로 View는 모든 걸 알고 시그널로 엮는다. 이렇게 하면 아래 계층을 독립적으로 테스트할 수 있다.

용어
액터(actor) 모델 / 단일 작성자(single writer)
각 상태 조각을 "오직 한 객체만 수정할 수 있게" 못 박는 설계. GD3에서 task.status는 큐 전이면 taskService가, 최종 결과(완료/실패)면 Step이 쓰고 View는 읽기만 한다. 상태를 누가 언제 바꾸는지 헷갈리는 버그(경쟁 조건)를 원천 차단한다.

4-2. 작업 하나가 태어나는 경로 — 파서 우선순위 체인

URL을 붙여넣으면 featureService.parse(options)가 등록된 파서들을 우선순위 낮은 것부터 훑으며 "이 URL 내가 처리할게"라고 처음 손드는 파서에게 맡긴다. 구체적인 것일수록 번호가 작아 먼저 검사되고, 무엇에도 안 걸리면 마지막의 HTTP 파서(100)가 받는다.

URL 입력 → featureService.parse(options) │ (우선순위 오름차순으로 match() 검사, 첫 매치 승) ├─ 45 ED2kParser ed2k:// 스킴 ├─ 50 BilibiliParser 비리비리 도메인 ├─ 70 YouTubeParser 유튜브 도메인 ├─ 80 M3U8Parser .m3u8 / .mpd ├─ 85 TorrentParser magnet: 또는 .torrent ├─ 85 HuggingFaceParser 허깅페이스 도메인 ├─ 90 GitHubParser github 파일 URL + 프록시 설정됨 ├─ 95 FtpParser ftp / ftps └─ 100 HttpParser 아무 http/https ← 최종 폴백

재미있는 지점: GitHubParser는 스스로 다운로드하지 않는다. URL을 프록시 주소로 바꾼 뒤 featureService.parse()를 다시 호출한다. 프록시 URL은 더 이상 github 호스트가 아니므로 HTTP 파서가 받는다 — "위임으로 기능을 조합"하는 깔끔한 패턴이다. 무한 재귀도 자연스럽게 피한다.

4-3. 무병합 조각 병렬 수신 — 실제 엔진 코드

이제 심장부다. HTTP 파일 하나를 어떻게 8조각으로 나눠 병합 없이 받는지, 실제 features/http_pack/task.py를 보자. 먼저 파일을 조각(Subworker)으로 나누는 부분:

# 파일 크기를 subworkerCount(기본 8)만큼 균등 분할
count = min(self.subworkerCount, self.fileSize)
subworkers = []
for i in range(count - 1):
    start = i * chunkSize
    end   = start + chunkSize - 1
    subworkers.append(HttpSubworker(index=i, start=start, end=end))
# 마지막 조각은 파일 끝까지 담당
subworkers.append(HttpSubworker(index=count-1, start=start, end=self.fileSize-1))

HttpSubworkerstart(담당 시작 바이트), end(끝 바이트), receivedBytes(지금까지 받은 양)만 든 작은 값이다. position = start + receivedBytes가 "다음에 써야 할 위치"다. 그리고 각 조각을 실제로 받는 _runSubworkerHTTP Range 요청으로 자기 구간만 달라고 하고, 받은 조각을 pwrite로 파일의 정확한 위치에 바로 쓴다:

# 서버에 "이 바이트 구간만 줘"라고 요청 (Range 헤더)
headers = {**self._effectiveHeaders,
           "range": f"bytes={subworker.position}-{subworker.end}"}
async for chunk in response.stream():
    # 받은 조각을 "그 위치에" 바로 써넣음 → 병합 불필요
    pwrite(fd, chunk, subworker.position)   # ← 핵심 한 줄
    subworker.receivedBytes += len(chunk)
    speedMeter.addSpeed(len(chunk))          # 속도계에 바이트 보고
    await speedMeter.waitForSpeedLimit()      # 전역 속도제한 준수
용어
HTTP Range 요청 · pwrite
Range 요청은 "파일 전체 말고 100~200바이트만 줘"라고 서버에 요청하는 HTTP 표준 기능(Range: bytes=100-200). 서버가 이걸 지원해야 조각 병렬 수신이 가능하다. pwrite는 "파일 포인터를 옮기지 않고 지정한 오프셋에 바로 쓰는" 시스템 콜. 여러 조각이 동시에 서로 다른 위치에 써도 안전하다.

4-4. "AI 스마트 가속"의 실제 정체

2장에서 예고한 자동 가속 루프의 실제 코드다. 1초마다 도는 감시자(_supervise)가 _autoSpeedUp()을 호출한다. 로직을 우리말로 풀면:

# 1) 최근 5초 속도를 모아 평균과 변동폭 계산
if maxDeviation > 0.15:   # 속도가 15% 넘게 출렁이면 판단 보류 (불안정)
    return

# 2) 첫 판단: 기준 속도/조각수 기록하고 조각 4개 추가 분할
if self._accelCheckTime == 0:
    self._accelInitialWorkers = len(self.subworkers)
    self._accelInitialSpeed   = avgSpeed
    for _ in range(4):
        self._reassignSubworker()      # 가장 느린 조각을 반으로 쪼갬
else:
    # 3) 5초 뒤 재평가: 조각을 N% 늘렸는데 속도는 얼마나 올랐나?
    workerRatio = (지금조각수 - 초기조각수) / 초기조각수
    speedRatio  = (지금속도  - 초기속도)  / 초기속도
    if speedRatio < 0.8 * workerRatio:
        self.isAccelerated = True     # 효과 없음 → 가속 종료
    else:
        self._accelCheckTime = 0        # 효과 있음 → 다음 라운드 계속

여기서 _reassignSubworker()는 "남은 양이 가장 많은(=가장 느린) 조각"을 찾아 그 구간을 반으로 쪼개고, 새 조각을 TaskGroup에 즉시 던져 넣는다. 즉 느린 조각을 실시간으로 더 잘게 나눠 병렬성을 높이는 방식이다. "AI"라는 이름과 달리 신경망은 없지만, 피드백을 측정해 스스로 파라미터를 조절하는 제어 루프라는 점에서 자동화된 튜닝이 맞다.

설계의 미학
이득이 없으면 멈추는 "탐욕적 자기 제한"

무작정 조각을 늘리면 서버 연결 수 제한이나 CPU 오버헤드로 오히려 느려질 수 있다. GD3는 "늘린 만큼 빨라지지 않으면 즉시 멈춘다"는 간단한 규칙으로 과최적화를 스스로 방지한다. 복잡한 AI 없이도 실용적 자동 튜닝이 가능함을 보여주는 좋은 예다.

4-5. 재개(resume)는 어떻게 — .ghd 진행 파일

다운로드를 껐다 켜도 이어받으려면 "어디까지 받았는지"를 디스크에 남겨야 한다. GD3는 감시자가 1초마다 각 조각의 (start, position, end) 삼중값을 이진 형식(struct.pack, 리틀엔디안 64비트)으로 파일명.ghd에 덮어쓴다. 다시 켜면 이 .ghd를 읽어 각 조각을 멈춘 지점부터 되살린다. 텍스트 JSON이 아니라 고정폭 이진이라 쓰기가 빠르고 파싱이 단순하다.

비유

여러 사람이 각자 다른 층에서 계단을 오르다 정전이 됐을 때, "누가 몇 층 몇 계단에 서 있었는지"를 종이 한 장(.ghd)에 초마다 갱신해 두는 것과 같다. 전기가 들어오면 각자 적힌 자리로 돌아가 이어서 오른다.

5디렉토리 구조 해부

폴더 이름만 봐도 아키텍처가 읽히도록 정리돼 있다
Ghost-Downloader-3/ ├─ Ghost-Downloader-3.py ← 데스크톱 진입점 (환경 셋업 + 앱 조립) ├─ CONTEXT.md ← ★ 32KB 설계 문서(용어·아키텍처·명명규칙) ├─ pyproject.toml / uv.lock ← 의존성 정의 + 잠금 │ ├─ app/ ← 플랫폼 공용 "엔진" │ ├─ startup.py ← loadEngine/loadPacks/startEngine/stopEngine │ ├─ client.py ← wreq HTTP 클라이언트 팩토리(TLS 위장) │ ├─ signal_bus.py ← 프로세스 전역 이벤트 버스(시그널 4개) │ ├─ services/ ← ★ 비즈니스 액터들 │ │ ├─ task_service.py (유일한 공개 창구) │ │ ├─ feature_service.py (팩 발견·파서 라우팅) │ │ ├─ speed_meter.py (속도 집계·제한) │ │ ├─ coroutine_runner.py (asyncio 루프 브리지) │ │ ├─ aria2_rpc.py (aria2 호환 RPC 서버) │ │ ├─ browser_service.py (확장과의 WebSocket) │ │ └─ category_service.py · clipboard_listener.py · plan.py │ ├─ models/ ← Task / TaskStep / Pack (데이터 정의) │ ├─ platform/ ← OS별 코드(windows·android·sysio·filesystem) │ ├─ view/ ← Qt 화면(windows·pages·cards·dialogs·mobile) │ └─ config/ ← 설정·경로·상수 │ ├─ features/ ← ★ 프로토콜 플러그인(팩) 12종 │ ├─ http_pack/ (task.py·pack.py·cards.py·manifest.toml) │ ├─ bittorrent_pack/ (session.py·metadata.py·web_tracker/…) │ ├─ m3u8_pack/ · ftp_pack/ · ed2k_pack/ · bili_pack/ │ ├─ yt_dlp_pack/ · github_pack/ · huggingface_pack/ │ └─ ffmpeg_pack/ · disk_pack/ │ ├─ browser_extension/ ← TS+React 확장(background·content·popup) ├─ android/ ← 안드로이드 패키징 ├─ scripts/ · tests/ · docs/ └─ LICENSE (GPL v3.0)

구조에서 읽어야 할 것: 플랫폼 공용 로직(app)과 프로토콜별 확장(features)이 확실히 분리돼 있고, 팩마다 manifest.toml이 있어 "이 팩이 무엇을 의존하고 무엇을 제공하는가"를 선언한다. featureService.load()가 이 매니페스트들을 읽어 의존성 순서대로(위상 정렬) 임포트한다. 새 프로토콜을 추가하려면 features/ 아래 폴더 하나만 만들면 되는 구조다.

용어
위상 정렬(topological sort) · 매니페스트
매니페스트는 플러그인이 "내 이름·의존성·기여 기능"을 적어 두는 선언 파일(manifest.toml). 위상 정렬은 "A가 B를 의존하면 B를 먼저"처럼 의존 관계를 지켜 순서를 정하는 알고리즘. 플러그인들을 올바른 순서로 로드할 때 쓴다.

6학습 포인트 — 기술별 배울 것

이 저장소를 교재로 삼으면 무엇을 익힐 수 있나

① asyncio와 GUI(Qt)를 안전하게 잇는 법

GUI 앱의 고질적 난제 — 무거운 네트워크 작업을 하면 화면이 멈춘다. GD3는 coroutineRunner별도 QThread에서 asyncio 루프를 돌리고, 결과만 Qt 메인 스레드로 콜백을 넘겨 화면 갱신을 안전하게 한다. "이벤트 루프 두 개(asyncio + Qt)를 어떻게 공존시키나"의 실전 정답지다.

실습 아이디어

PySide6 + asyncio로 "URL을 넣으면 백그라운드에서 파일 크기를 HEAD 요청으로 알아내 라벨에 표시하는" 최소 앱을 만들어 보라. coroutine_runner.py가 쓰는 패턴(스레드 분리 + 콜백 전달)을 따라 하면 화면이 안 멈추는 걸 체감할 수 있다.

② HTTP Range·pwrite로 병렬 다운로드 직접 구현

4장의 무병합 엔진은 그 자체로 훌륭한 학습 주제다. Range 헤더로 구간을 요청하고 os.pwrite로 오프셋에 쓰는 것만 알면, 100줄 안팎의 미니 병렬 다운로더를 스스로 만들 수 있다. 서버가 Range를 지원하지 않을 때(단일 스트림)의 폴백 처리까지 보면 실전 감각이 완성된다.

③ 액터 아키텍처와 "단일 작성자" 규율

CONTEXT.md는 그 자체로 대규모 앱을 어떻게 층으로 나누고, 상태 소유권을 어떻게 못 박는지에 대한 교과서다. "매니저·컨트롤러·프로바이더 같은 구조적 이름을 쓰지 말고, 도메인 개념으로 이름 지어라(taskService, speedMeter)" 같은 명명 철학까지 담겨 있다. 협업 코드의 이름 짓기로 고민해 본 개발자에게 특히 값지다.

④ 플러그인 시스템 설계

매니페스트 선언 → 위상 정렬 → 우선순위 기반 라우팅으로 이어지는 featureService는 "확장 가능한 앱"의 표본이다. 파서가 스스로 하지 않고 parse()를 재귀 호출해 위임하는 GitHubParser·MergeParser의 조합 패턴도 눈여겨볼 만하다.

⑤ 브라우저 확장(MV3)과 데스크톱 앱의 통신

확장은 WebSocket 프로토콜 v3(hello → hello_ack → subscribe → snapshot)로 앱과 대화하고, 연결이 끊기면 작업을 큐에 쌓아뒀다 재연결 시 흘려보낸다. "브라우저 ↔ 네이티브 앱"을 잇는 실전 아키텍처를 통째로 읽을 수 있다.

배울 기술관련 파일난이도
asyncio ↔ Qt 브리지services/coroutine_runner.py
병렬 Range 다운로드features/http_pack/task.py중~상
액터·소유권 설계CONTEXT.md, services/task_service.py
플러그인 로딩services/feature_service.py, models/pack.py
TLS 지문 위장client.py (wreq)
MV3 확장 ↔ 앱browser_extension/, services/browser_service.py

7시스템 · 실행 요구사항

쓰려면(사용자) / 소스로 돌리려면(개발자) 무엇이 필요한가

사용자 — 미리 빌드된 앱 설치

플랫폼최소 버전아키텍처
Windows10 이상x86_64 / arm64
macOS13.0 이상x86_64 / arm64(애플 실리콘)
Linuxglibc 2.35 이상x86_64 / arm64
Android10.0 이상arm64-v8a
주의
Qt 6.6+ 는 AVX 명령어 없는 오래된 CPU를 지원하지 않는다

2013년 이전의 아주 오래된 CPU라면 실행이 안 될 수 있다. 또 Arch Linux는 커뮤니티 AUR 패키지(ghost-downloader-bin·ghost-downloader-git)로도 설치 가능하다.

개발자 — 소스에서 실행

# 1) uv 설치 후 저장소 클론
git clone https://github.com/XiaoYouChR/Ghost-Downloader-3.git
cd Ghost-Downloader-3

# 2) uv가 pyproject.toml/uv.lock 보고 가상환경+의존성 자동 구성
uv sync

# 3) 데스크톱 진입점 실행
uv run python Ghost-Downloader-3.py

플랫폼 조건부 의존성이 자동 처리된다 — 윈도우면 winloop·libtorrent-windows-dll, 맥/리눅스면 uvloop, 맥이면 pyobjc가 각각 설치된다. 배포판을 직접 만들려면 nuitka로 컴파일하며, FFmpeg·N_m3u8DL-RE 같은 외부 바이너리는 앱이 필요할 때 Binary Runtime으로 내려받아 설치한다.

8직접 해볼 수 있는 실습 과제

읽기만 하지 말고 손으로 만들어 보는 난이도별 과제
난이도 ★ — 입문

미니 병렬 다운로더 (100줄)

Python httpx + os.pwrite만으로 "파일을 4조각으로 나눠 Range 요청으로 동시에 받아 병합 없이 저장하는" 스크립트를 작성하라. GD3의 _runSubworker를 축약판으로 재현하는 것이다. 서버가 Range를 지원하는지 Accept-Ranges 헤더로 먼저 확인하는 분기를 넣어 보라.

난이도 ★★ — 초중급

재개 가능한 다운로드 만들기

위 스크립트에 "진행 파일(.progress)" 저장을 추가하라. 각 조각의 (start, position, end)struct.pack으로 1초마다 기록하고, 중단 후 재실행 시 이 파일을 읽어 이어받게 하라. GD3의 _supervise가 하는 일을 그대로 축소 구현하는 과제다.

난이도 ★★★ — 중급

asyncio + Qt 화면 붙이기

PySide6로 진행률 바를 만들고, 백그라운드 스레드의 asyncio 다운로드가 시그널로 진행률을 화면에 밀어 넣게 하라. 화면이 절대 멈추지 않아야 한다. GD3의 coroutine_runner.py 패턴을 참고하라.

난이도 ★★★★ — 중상급

자동 가속 휴리스틱 이식

4장의 _autoSpeedUp 로직(5초 관찰 → 조각 추가 → workerRatio 대비 speedRatio 평가)을 자신의 다운로더에 이식하고, 대역폭 제한이 걸린 테스트 서버에서 실제로 속도가 오르는지 측정하라. "언제 멈춰야 하는가"의 임계값(0.8)을 바꿔가며 실험해 보라.

난이도 ★★★★★ — 도전

새 프로토콜 팩 추가

GD3를 클론해 features/에 새 팩 폴더를 만들고, manifest.toml + TaskParser를 정의해 특정 사이트 URL을 처리하는 파서를 등록하라. featureService의 우선순위 라우팅에 내 파서가 올바르게 끼어드는지 확인하는 것이 목표다.

9관련 기술 심화 학습 로드맵

이 저장소가 건드리는 기술들을 주차별로 파고드는 4주 코스
주차주제핵심 질문 · 실습
1주차HTTP 심화Range 요청·조건부 요청(If-Range)·청크 전송·Content-Disposition 파싱. 브라우저 개발자도구 Network 탭으로 실제 헤더를 관찰
2주차파이썬 비동기asyncio 이벤트 루프·TaskGroup·CancelledError 취소 전파. uvloop이 왜 빠른지. GD3의 coroutine_runner 읽기
3주차Qt/PySide6 GUI시그널·슬롯, 스레드 간 통신, 가상 스크롤(TaskPage). 화면 안 멈추게 만드는 원칙
4주차아키텍처·확장성액터/단일 작성자·플러그인 로딩·의존성 방향. CONTEXT.md 전체를 정독하고 내 프로젝트에 이름 규칙 적용

여력이 되면 보너스로 TLS 지문(JA3/JA4)비트토렌트 프로토콜(libtorrent 세션·트래커·조각 교환)을 각각 하루씩 파보면, GD3가 왜 그렇게 많은 라이브러리를 조합했는지 전모가 보인다.

10핵심 키워드 사전

이 문서에서 나온 용어를 한 번에 정리
용어
멀티스레드 다운로드파일을 여러 조각으로 나눠 동시 연결로 받아 속도를 높이는 방식
무병합(no-merge)조각을 따로 받았다 합치지 않고, 받는 즉시 최종 파일의 제 위치에 직접 쓰는 설계
SubworkerHTTP/FTP 작업에서 한 바이트 구간을 담당하는 분할 전송 단위
HTTP Range 요청파일의 특정 바이트 구간만 달라고 서버에 요청하는 표준 기능(Range: bytes=a-b)
pwrite파일 포인터를 옮기지 않고 지정 오프셋에 바로 쓰는 시스템 콜(동시 쓰기 안전)
AI 스마트 가속속도를 측정해 조각 수를 자동 조절하는 피드백 튜닝 루프(신경망 아님)
TLS 지문 위장다운로더 요청을 실제 브라우저의 TLS 악수처럼 보이게 해 봇 차단을 피하는 기법
액터(actor)특정 상태를 독점 소유하고 그 상태의 유일한 작성자가 되는 서비스 객체
단일 공개 창구작업 생성·변경을 taskService 한 곳으로만 통하게 좁힌 규율
FeaturePack프로토콜별 기능을 담은 플러그인 번들(파서·카드·런타임·페이지 기여)
파서 우선순위URL을 어떤 파서가 처리할지 낮은 번호부터 검사해 첫 매치가 이기는 라우팅
.ghd 진행 파일각 조각의 (start,position,end)를 이진으로 저장해 재개를 가능케 하는 파일
aria2 RPC인기 다운로더 aria2와 호환되는 JSON-RPC 인터페이스(외부 도구 연동용)
MV3 서비스워커크롬 확장 매니페스트 v3의 백그라운드 실행 단위. 미디어 스니핑·앱 통신 담당

11참고 링크

더 깊이 파고들 때의 출발점
자료링크 · 설명
GitHub 저장소github.com/XiaoYouChR/Ghost-Downloader-3 — 소스·이슈·릴리스
공식 문서gd.xychr.com/en/docs/ — 사용 설명서(한/영/중)
CONTEXT.md저장소 루트 — 아키텍처·용어·명명 규칙 정리(필독)
wreqgithub.com/0x676e67/wreq-python — TLS 지문 위장 HTTP 클라이언트
libtorrentgithub.com/arvidn/libtorrent — 비트토렌트 C++ 구현
yt-dlpgithub.com/yt-dlp/yt-dlp — 유튜브 등 미디어 다운로드 엔진
PyQt-Fluent-Widgetsgithub.com/zhiyiYo/PyQt-Fluent-Widgets — Fluent 스타일 위젯
uvloopgithub.com/MagicStack/uvloop — 초고속 asyncio 이벤트 루프