1#!/usr/bin/env python3
2
3# Copyright (c) 2009 Giampaolo Rodola'. All rights reserved.
4# Use of this source code is governed by a BSD-style license that can be
5# found in the LICENSE file.
6
7"""
8Script which downloads wheel files hosted on AppVeyor:
9https://ci.appveyor.com/project/giampaolo/psutil
10Readapted from the original recipe of Ibarra Corretge'
11<saghul@gmail.com>:
12http://code.saghul.net/index.php/2015/09/09/
13"""
14
15from __future__ import print_function
16import concurrent.futures
17import os
18import requests
19import sys
20
21from psutil import __version__ as PSUTIL_VERSION
22from psutil._common import bytes2human
23from psutil._common import print_color
24
25
26USER = "giampaolo"
27PROJECT = "psutil"
28BASE_URL = 'https://ci.appveyor.com/api'
29PY_VERSIONS = ['2.7', '3.6', '3.7', '3.8', '3.9']
30TIMEOUT = 30
31
32
33def download_file(url):
34    local_fname = url.split('/')[-1]
35    local_fname = os.path.join('dist', local_fname)
36    os.makedirs('dist', exist_ok=True)
37    r = requests.get(url, stream=True, timeout=TIMEOUT)
38    tot_bytes = 0
39    with open(local_fname, 'wb') as f:
40        for chunk in r.iter_content(chunk_size=16384):
41            if chunk:    # filter out keep-alive new chunks
42                f.write(chunk)
43                tot_bytes += len(chunk)
44    return local_fname
45
46
47def get_file_urls():
48    with requests.Session() as session:
49        data = session.get(
50            BASE_URL + '/projects/' + USER + '/' + PROJECT,
51            timeout=TIMEOUT)
52        data = data.json()
53
54        urls = []
55        for job in (job['jobId'] for job in data['build']['jobs']):
56            job_url = BASE_URL + '/buildjobs/' + job + '/artifacts'
57            data = session.get(job_url, timeout=TIMEOUT)
58            data = data.json()
59            for item in data:
60                file_url = job_url + '/' + item['fileName']
61                urls.append(file_url)
62        if not urls:
63            print_color("no artifacts found", 'red')
64            sys.exit(1)
65        else:
66            for url in sorted(urls, key=lambda x: os.path.basename(x)):
67                yield url
68
69
70def rename_win27_wheels():
71    # See: https://github.com/giampaolo/psutil/issues/810
72    src = 'dist/psutil-%s-cp27-cp27m-win32.whl' % PSUTIL_VERSION
73    dst = 'dist/psutil-%s-cp27-none-win32.whl' % PSUTIL_VERSION
74    print("rename: %s\n        %s" % (src, dst))
75    os.rename(src, dst)
76    src = 'dist/psutil-%s-cp27-cp27m-win_amd64.whl' % PSUTIL_VERSION
77    dst = 'dist/psutil-%s-cp27-none-win_amd64.whl' % PSUTIL_VERSION
78    print("rename: %s\n        %s" % (src, dst))
79    os.rename(src, dst)
80
81
82def run():
83    urls = get_file_urls()
84    completed = 0
85    exc = None
86    with concurrent.futures.ThreadPoolExecutor() as e:
87        fut_to_url = {e.submit(download_file, url): url for url in urls}
88        for fut in concurrent.futures.as_completed(fut_to_url):
89            url = fut_to_url[fut]
90            try:
91                local_fname = fut.result()
92            except Exception:
93                print_color("error while downloading %s" % (url), 'red')
94                raise
95            else:
96                completed += 1
97                print("downloaded %-45s %s" % (
98                    local_fname, bytes2human(os.path.getsize(local_fname))))
99    # 2 wheels (32 and 64 bit) per supported python version
100    expected = len(PY_VERSIONS) * 2
101    if expected != completed:
102        return exit("expected %s files, got %s" % (expected, completed))
103    if exc:
104        return exit()
105    rename_win27_wheels()
106
107
108def main():
109    run()
110
111
112if __name__ == '__main__':
113    main()
114