Last active
January 15, 2019 14:09
-
-
Save chwnam/a97c5b6c70d274a5ecdaf97761aed6b1 to your computer and use it in GitHub Desktop.
드롭박스 공개 공유 URL로부터 모든 하위 폴더에 위치한 파일의 조사, aria2c 프로그램으로 다운로드 받을 수 있도록 인자를 만들어 내는 스크립트입니다.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| # /usr/bin/env python2 | |
| # -*- coding: utf-8 -*- | |
| import argparse | |
| import csv | |
| import json | |
| import os | |
| import sys | |
| import dropbox | |
| def parse_args(): | |
| parser = argparse.ArgumentParser( | |
| description='드롭박스 공개 공유 링크 URL 조사 스크립트. ' + | |
| 'access token 문자열은 반드시 환경 변수 \'dropbox_access_token\'으로 지정해야 합니다.' | |
| ) | |
| parser.add_argument( | |
| '-c', '--command', | |
| choices=['tree', 'merge', 'aria2'], | |
| help='작업을 지시합니다. tree 명령은 공개 드롭박스 URL에서 재귀적으로 순회하면서 파일의 주요 메타데이터를 추출합니다. ' + | |
| 'merge 명령은 두 개의 tree 작업 결과 CSV 파일을 하나로 합칠 때 사용합니다.' + | |
| 'aria2 명령은 입력 파일로부터 aria2 --input-file 파라미터를 위한 출력을 가공합니다.' | |
| ) | |
| parser.add_argument( | |
| '-i', '--input', | |
| type=argparse.FileType('rb'), | |
| default=None, | |
| help='tree 명령 사용시, 이 프로그램이 이전에 출력한 파일을 입력으로 사용할 수 있습니다. ' + | |
| '이 경우 이 파일을 기준으로 하여, 변경된 파일만 출력으로 나옵니다. ' + | |
| 'merge 명령 사용시, 기준 CSV 파일을 지정할 때 사용합니다. ' + | |
| 'aria2 명령 사용시, 다운로드 받을 파일 목록을 입력합니다.' | |
| ) | |
| parser.add_argument( | |
| '-r', '--incremental', | |
| type=argparse.FileType('rb'), | |
| default=None, | |
| help='merge 명령에만 유효합니다. 새롭게 변경된 파일 목록을 지정합니다. ' + | |
| '기준의 파일 내용에 이 내용을 덮어 씌워 새 파일로 만듭니다.' | |
| ) | |
| parser.add_argument( | |
| '-o', '--output', | |
| type=argparse.FileType('wb'), | |
| default=sys.stdout, | |
| help='명령의 결과를 출력합니다. tree, merge 명령의 출력은 CSV 포맷입니다. aria2c 명령은 일반 텍스트 출력입니다.' | |
| ) | |
| parser.add_argument( | |
| '--file-limit', | |
| type=int, | |
| default=-1, | |
| help='tree 명령시 지정한 개수의 파일만 조사하고 마칩니다. 시험 용도입니다.' | |
| ) | |
| parser.add_argument( | |
| '--link', | |
| help='작업할 드롭박스의 공개 URL 주소를 입력합니다. tree 작업시 필수적으로 입력해야 합니다.', | |
| default='' | |
| ) | |
| return parser.parse_args() | |
| class DropboxSharedLinkTask(object): | |
| """ | |
| see: http://dropbox-sdk-python.readthedocs.io/en/latest/ | |
| """ | |
| def __init__(self, access_token, shared_link_url, limit=-1): | |
| self.access_token = '' | |
| if access_token: | |
| self.access_token = access_token | |
| self.dbx = dropbox.Dropbox(access_token) | |
| if shared_link_url: | |
| self.shared_link = dropbox.files.SharedLink(shared_link_url) | |
| self.tree_entries = [] | |
| self.reference = {} | |
| self.file_limit = limit | |
| self.file_count = 0 | |
| self.field_names = ['path', 'id', 'name', 'rev', 'size', 'content_hash', 'url'] | |
| @staticmethod | |
| def is_file(metadata): | |
| return isinstance(metadata, dropbox.files.FileMetadata) | |
| @staticmethod | |
| def is_directory(metadata): | |
| return isinstance(metadata, dropbox.files.FolderMetadata) | |
| def get_attachment_item_from_file_metadata(self, path, metadata): | |
| return dict( | |
| zip( | |
| self.field_names, | |
| [ | |
| (path + '/' + metadata.name), | |
| metadata.id, | |
| metadata.name, | |
| metadata.rev, | |
| metadata.size, | |
| metadata.content_hash, | |
| '' | |
| ] | |
| ) | |
| ) | |
| def tree(self, path): | |
| # shared link 에서는 recursive 가 먹히지 않는다! | |
| response = self.dbx.files_list_folder(path, shared_link=self.shared_link) | |
| for entry in response.entries: | |
| if self.file_count == self.file_limit: | |
| return | |
| if self.is_file(entry): | |
| key = path + '/' + entry.name | |
| skip = self.reference and (key in self.reference) and (entry.rev == self.reference[key]['rev']) | |
| if not skip: | |
| print('retrieving FILE %s' % entry.name) | |
| self.tree_entries.append(self.get_attachment_item_from_file_metadata(path, entry)) | |
| self.file_count += 1 | |
| elif self.is_directory(entry): | |
| print('retrieving DIR %s' % entry.name) | |
| self.tree(path + '/' + entry.name) | |
| def retrieve_tree(self, reference): | |
| self.tree_entries = [] | |
| self.reference = {} | |
| self.file_count = 0 | |
| if reference: | |
| reader = csv.DictReader(reference, fieldnames=self.field_names) | |
| next(reader) | |
| for row in reader: | |
| self.reference[row['path']] = row | |
| self.tree('') | |
| def export_entries(self, fp, entries=None): | |
| writer = csv.DictWriter(fp, fieldnames=self.field_names) | |
| writer.writeheader() | |
| if not entries: | |
| entries = self.tree_entries | |
| writer.writerows(entries) | |
| def merge(self, reference, incremental, output): | |
| reader = csv.DictReader(reference, fieldnames=self.field_names) | |
| next(reader) | |
| ref = [row for row in reader] | |
| reader = csv.DictReader(incremental, fieldnames=self.field_names) | |
| next(reader) | |
| inc = {row['path']: row for row in reader} | |
| merged = [] | |
| for row in ref: | |
| path = row['path'] | |
| if path in inc and row['rev'] != inc[path]['rev']: | |
| merged.append(inc[path]) | |
| else: | |
| merged.append(row) | |
| writer = csv.DictWriter(output, fieldnames=self.field_names) | |
| writer.writeheader() | |
| writer.writerows(merged) | |
| def aria2_input_file(self, input_file, output_file): | |
| reader = csv.DictReader(input_file, fieldnames=self.field_names) | |
| next(reader) | |
| items = [row for row in reader] | |
| for item in items: | |
| output_file.write('https://content.dropboxapi.com/2/sharing/get_shared_link_file' + '\n') | |
| output_file.write('\theader=Authorization: Bearer %s\n' % self.access_token) | |
| output_file.write('\theader=Dropbox-API-Arg: %s\n' % ( | |
| json.dumps({ | |
| 'url': self.shared_link.url, | |
| 'path': item['path'] | |
| }) | |
| )) | |
| output_file.write('\tout=%s\n' % item['path'].strip('/')) | |
| print('aria2c 명령어는 https://aria2.github.io/manual/en/html/aria2c.html 페이지를 참고하세요.') | |
| print('다음 옵션은 꼭 고려하여 사용하세요.') | |
| print('-d, --dir=<DIR> 다운로드 파일을 받을 디렉토리.') | |
| print('-i, --input-file=<FILE> 이 명령의 결과물을 입력하기 위해.') | |
| print('-j, --max-concurrent-downloads=<N> 동시 다운로드 수 조정.') | |
| print('--dry-run [true|false] true면 다운로드 시도만 해 봅니다.') | |
| print('--allow-overwrite [true|false] 덮어쓰기를 할지 말지 결정합니다.') | |
| print('--auto-file-renaming [true|false] 자동으로 이름 고치기를 허용할지 결정합니다.') | |
| def interruptable_task(try_callback, finally_callback): | |
| message = '' | |
| try: | |
| try_callback() | |
| except KeyboardInterrupt: | |
| message = '사용자의 요청에 의해 작업이 중간에 중단되었습니다. 모든 결과가 저장되지 않은 점 주의하시기 바랍니다.' | |
| # except Exception as e: | |
| # message = '에러로 인해 작업이 중간에 중단되었습니다. 모든 결과가 저장되지 않은 점 주의하시기 바랍니다.' | |
| finally: | |
| if message: | |
| print(message) | |
| finally_callback() | |
| if __name__ == '__main__': | |
| parsed = parse_args() | |
| task = DropboxSharedLinkTask( | |
| access_token=os.environ.get('dropbox_access_token'), | |
| shared_link_url=parsed.link, | |
| limit=parsed.file_limit | |
| ) | |
| def tree_callback(): | |
| task.retrieve_tree(parsed.input) | |
| def finish_task(): | |
| task.export_entries(parsed.output) | |
| if parsed.command == 'tree': | |
| interruptable_task(tree_callback, finish_task) | |
| elif parsed.command == 'merge': | |
| task.merge(parsed.input, parsed.incremental, parsed.output) | |
| elif parsed.command == 'aria2': | |
| task.aria2_input_file(parsed.input, parsed.output) |
CSV 파일의 칼럼 정보입니다.
- path: 공유 URL로부터의 경로입니다.
- id: 내부적으로 사용하는 이 파일에 대한 유일한 식별자입니다.
- name: 파일 이름, 디렉토리 이름입니다.
- rev: 리비전 정보입니다. 이 문자열이 다르다면 같은 파일이 업데이트 된 것입니다.
- size: 파일의 크기
- content_hash: 파일의 해시. 해시 정보는 https://www.dropbox.com/developers/reference/content-hash 여기를 참고하세요.
- url: 사용하지 않습니다.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
세팅은 이렇습니다.
간단한 사용법입니다.
목록 생성하기
python dropbox_attachments.py --command=tree --output='url_list.csv' --link='https://dropbox.com/.....'해당 공유 폴더를 재귀적으로 돌면서 모든 파일의 목록을 조사해 url_list.csv 파일로 추출합니다.
증분 목록 생성하기
python dropbox_attachments.py --commend=tree --input='url_list.csv' --output='updated.csv' --link='https://dropbox.com/.....'해당 공유 폴더를 재귀적으로 도는 것까지는 같으나, input으로 준 파일과 대조하여, input에 없는 내용, 혹은 리비전이 변경된 (즉 더 새로운 파일) 파일만을 추출해 updated.csv 로 추출합니다.
기본/증분 목록 합치기
python dropbox_attachments.py --commend=merge --input='url_list.csv' --incremental 'updated.csv' --output='new_url_list.csv' --link='https://dropbox.com/.....'기존의 url_list.csv 파일과 update.csv 파일을 합쳐 updated의 최신 내용을 url_list와 합칩니다. 결과는 new_url_list.csv로 저장합니다.
aria2c 다운로드 유틸리티를 위한 input file 생성하기
다운로드는 전문 다운로드 유틸리티에게 맡기는 것이 더욱 효율적입니다. aria2가 목록을 직접 다운로드 할 수 있도록 csv 파일을 가공하여 input-file 파라미터로 사용할 파일을 생성하도록 도와줍니다.
python dropbox_attachments.py --command=aria2 --input 'url_list.csv' --output='aria2.txt' --link='https://dropbox.com/.....'이렇게 만들어진 aria2.txt는 다음처럼 사용 가능합니다.
aria2c --input-file=aria2.txt --dir='./downloads'