]> www.average.org Git - loctrkd.git/blob - gps303/ocid_dload.py
0bdb8b70e29e916147f94b56d502c500141bc677
[loctrkd.git] / gps303 / ocid_dload.py
1 from configparser import NoOptionError
2 import csv
3 from logging import getLogger
4 import requests
5 from sqlite3 import connect
6 from zlib import decompressobj, MAX_WBITS
7
8 from . import common
9
10 log = getLogger("gps303/ocid_dload")
11
12 RURL = (
13     "https://opencellid.org/ocid/downloads"
14     "?token={token}&type={dltype}&file={fname}.csv.gz"
15 )
16
17 SCHEMA = """create table if not exists cells (
18   "radio" text,
19   "mcc" int,
20   "net" int,
21   "area" int,
22   "cell" int,
23   "unit" int,
24   "lon" int,
25   "lat" int,
26   "range" int,
27   "samples" int,
28   "changeable" int,
29   "created" int,
30   "updated" int,
31   "averageSignal" int
32 )"""
33 DBINDEX = "create index if not exists cell_idx on cells (area, cell)"
34
35
36 class unzipped:
37     """
38     File-like object that unzips http response body.
39     read(size) method returns chunks of binary data as bytes
40     When used as iterator, splits data to lines
41     and yelds them as strings.
42     """
43
44     def __init__(self, zstream):
45         self.zstream = zstream
46         self.decoder = decompressobj(16 + MAX_WBITS)
47         self.outdata = b""
48         self.line = b""
49
50     def read(self, n=None):
51         if self.decoder is None:
52             return b""
53         while len(self.outdata) < n:
54             raw_data = self.zstream.read(n)
55             self.outdata += self.decoder.decompress(raw_data)
56             if not raw_data:
57                 self.decoder = None
58                 break
59         if self.outdata:
60             data, self.outdata = self.outdata[:n], self.outdata[n:]
61             return data
62         return b""
63
64     def __next__(self):
65         while True:
66             splittry = self.line.split(b"\n", maxsplit=1)
67             if len(splittry) > 1:
68                 break
69             moredata = self.read(256)
70             if not moredata:
71                 raise StopIteration
72             self.line += moredata
73         line, rest = splittry
74         self.line = rest
75         return line.decode("utf-8")
76
77     def __iter__(self):
78         return self
79
80
81 def main(conf):
82     try:
83         url = conf.get("opencellid", "downloadurl")
84         mcc = "<unspecified>"
85     except NoOptionError:
86         try:
87             with open(
88                 conf.get("opencellid", "downloadtoken"), encoding="ascii"
89             ) as fl:
90                 token = fl.read().strip()
91         except FileNotFoundError:
92             log.warning(
93                 "Opencellid access token not configured, cannot download"
94             )
95             return
96         mcc = conf.get("opencellid", "downloadmcc")
97         if mcc == "full":
98             dltype = "full"
99             fname = "cell_towers"
100         else:
101             dltype = "mcc"
102             fname = mcc
103         url = RURL.format(token=token, dltype="mcc", fname=mcc)
104     dbfn = conf.get("opencellid", "dbfn")
105     count = 0
106     with requests.get(url, stream=True) as resp, connect(dbfn) as db:
107         log.debug("Requested %s, result %s", url, resp)
108         if resp.status_code != 200:
109             log.error("Error getting %s: %s", url, resp)
110             return
111         db.execute("pragma journal_mode = wal")
112         db.execute(SCHEMA)
113         db.execute("delete from cells")
114         rows = csv.reader(unzipped(resp.raw))
115         for row in rows:
116             db.execute(
117                 """insert into cells
118                    values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
119                 row,
120             )
121             count += 1
122         if count < 1:
123             db.rollback()
124             log.warning("Did not get any data for MCC %s, rollback", mcc)
125         else:
126             db.execute(DBINDEX)
127             db.commit()
128             log.info(
129                 "repopulated %s with %d records for MCC %s", dbfn, count, mcc
130             )
131
132
133 if __name__.endswith("__main__"):
134     main(common.init(log))