import-from-xon.py 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. import re, argparse
  2. import sqlite3 as sql
  3. import logging
  4. import logging.handlers
  5. from os import listdir, mkdir
  6. from os.path import isfile, exists
  7. from urllib.parse import unquote
  8. #------------------------------------------------+
  9. # get_list_from_server_txt
  10. #------------------------------------------------+
  11. # Rows in the game server database are
  12. # occasionally concatenated into one line.
  13. # To simplify, they are unmerged.
  14. #
  15. # The final result is every row in the game server
  16. # database with its own index in a list.
  17. #------------------------------------------------+
  18. def get_list_from_server_txt(filename):
  19. def unmerge_rows(line, char, x):
  20. chunks = line.split(char)
  21. newrows = [char.join(chunks[:x]), char.join(chunks[x:])]
  22. # need to prefix each row with a char.
  23. # only the last row will be missing it.
  24. newrows[-1] = char + newrows[-1]
  25. if newrows[-1].count(char) > (x - 1):
  26. newrows += unmerge_rows(newrows.pop(), char, x)
  27. return newrows
  28. rows = []
  29. with open(filename, 'r') as f:
  30. # server database has a lot of newlines to ignore
  31. rows = [line for line in f if line != "\n"]
  32. output = []
  33. n = 3
  34. backslash = '\\'
  35. for row in rows:
  36. # The first and last column is prefixed with a backslash.
  37. # So multiple rows on one line should be split at the 3rd backslash.
  38. if row.count(backslash) > (n - 1):
  39. unmerged = unmerge_rows(row, backslash, n)
  40. for u in unmerged:
  41. output.append(u)
  42. else:
  43. output.append(row)
  44. return output
  45. def init_logging():
  46. filename = "_logs/dbimport-%s.log"
  47. i = 0
  48. while exists(filename % i):
  49. i += 1
  50. filename = filename % i
  51. f = open(filename, mode='a', encoding='utf-8')
  52. logging.basicConfig(stream=f, level=logging.DEBUG)
  53. return filename
  54. #------------------------------------------------+
  55. # Functions: Clean up.
  56. #------------------------------------------------+
  57. # Unlike other rows,
  58. # the separator character, '/' is part of the value of the second column.
  59. # so an ordinary match for '/' or '\' can not be done like the other types of rows.
  60. # example from game server db:
  61. # \/uid2name/Mnumg2Yh/yxNFDTqGI+YyhlM7QDI0fpEmAaBJ8cI5dU=\Tuxxy
  62. # it should become:
  63. # ["uid2name", "Mnumg2Yh/yxNFDTqGI+YyhlM7QDI0fpEmAaBJ8cI5dU=", "Tuxxy"]
  64. def uid2namefix(row):
  65. # quick fix
  66. # replace first and last occurrence of backslash
  67. # this results in [,/uid2name/cryptoid_fp, name]
  68. e = re.sub(r'^([^\\]*)\\|\\(?=[^\\]*$)', ',', row)
  69. # replace first two occurence of forward slash
  70. # this results in [,,uid2name,cryptoid_fp, name]
  71. ee = e.replace('/', ',', 2)
  72. # split on comma
  73. # but start from index 2 because the first commas are left over
  74. # c is now a list of strings.
  75. # ["uid2name", <crypto_idfp value>, <player name value>]
  76. c = ee[2:].split(',')
  77. c[2] = unquote(c[2])
  78. c[2] = c[2].strip('\n')
  79. return c
  80. # O(n) and organize cts related data into list of rows.
  81. def filters(db):
  82. tt = []
  83. tr = []
  84. ti = []
  85. rank_index = 2
  86. for d in db:
  87. if d.find("uid2name") != -1:
  88. ti.append(uid2namefix(d))
  89. else:
  90. # regex:
  91. # find substrings that do not contain backslash, forwardslash, or newline.
  92. e = re.findall(r'[^\\/\n]+', d)
  93. if d.find("cts100record/time") != -1:
  94. e[rank_index] = int(e[rank_index].replace("time", ""))
  95. tt.append(e)
  96. if d.find("cts100record/crypto_idfp") != -1:
  97. e[3] = unquote(e[3])
  98. e[rank_index] = int(e[rank_index].replace("crypto_idfp", ""))
  99. tr.append(e)
  100. if d.find("cts100record/speed") != -1:
  101. # print(d)
  102. # speed records - not implemented
  103. pass
  104. return tt, tr, ti
  105. #------------------------------------------------+
  106. # Functions: Database Creation
  107. #------------------------------------------------+
  108. def inserttodb(c, q, d):
  109. for x in d:
  110. # possible to do executemany
  111. # but want to be able to catch the problematic rows
  112. # as it is iterated through.
  113. # and proceed with adding OK rows.
  114. try:
  115. c.execute(q, x)
  116. except sql.ProgrammingError as e:
  117. print(e)
  118. print(x)
  119. #------------------------------------------------+
  120. # insert new data directly into new database file
  121. def i(d, s):
  122. con = sql.connect(d)
  123. with con:
  124. csr = con.cursor()
  125. try:
  126. times, ranks, ids = filters(get_list_from_server_txt(s))
  127. if times:
  128. inserttodb(csr, "INSERT OR REPLACE INTO Cts_times VALUES(?, ?, ?, ?)", times)
  129. logging.info('\n'.join(y for y in [str(x) for x in times]))
  130. if ranks:
  131. inserttodb(csr, "INSERT OR REPLACE INTO Cts_ranks VALUES(?, ?, ?, ?)", ranks)
  132. logging.info('\n'.join(y for y in [str(x) for x in ranks]))
  133. if ids:
  134. inserttodb(csr, "INSERT OR REPLACE INTO Id2alias VALUES(?, ?, ?)", ids)
  135. logging.info('\n'.join(y for y in [str(x) for x in ids]))
  136. except sql.Error:
  137. logging.exception("sql error encountered in function 'i'")
  138. if con:
  139. con.rollback()
  140. # 'insert' new data into a file i.e sql query file
  141. def f(d, s):
  142. with open(d, 'w', encoding='utf-8') as h:
  143. times, ranks, ids = filters(get_list_from_server_txt(s))
  144. for t in times:
  145. h.write("INSERT OR REPLACE INTO Cts_times VALUES(%s, %s, %s, %s)\n" % tuple(t))
  146. pass
  147. for r in ranks:
  148. h.write("INSERT OR REPLACE INTO Cts_ranks VALUES(%s, %s, %s, %s)\n" % tuple(r))
  149. pass
  150. for i in ids:
  151. h.write("INSERT OR REPLACE INTO Id2aslias VALUES(%s, %s, %s)\n" % tuple(i))
  152. pass
  153. pass
  154. pass
  155. # Test whether repeat rows are added.
  156. def duplicatestest(d, s):
  157. c = sql.connect(d)
  158. p = True
  159. with c:
  160. cs = c.cursor()
  161. try:
  162. logging.info("Inserting into database (1/2)")
  163. i(d, s)
  164. logging.info("Querying (1/2)")
  165. cs.execute("SELECT * FROM Cts_times")
  166. a = cs.fetchall()
  167. cs.execute("SELECT * FROM Cts_ranks")
  168. b = cs.fetchall()
  169. cs.execute("SELECT * FROM Id2alias")
  170. c = cs.fetchall()
  171. logging.info("Inserting into database (2/2)")
  172. i(d, s)
  173. logging.info("Querying (2/2)")
  174. cs.execute("SELECT * FROM Cts_times")
  175. x = cs.fetchall()
  176. cs.execute("SELECT * FROM Cts_ranks")
  177. y = cs.fetchall()
  178. cs.execute("SELECT * FROM Id2alias")
  179. z = cs.fetchall()
  180. if len(a) != len(x):
  181. logging.error("Issue with Cts_times")
  182. p = False
  183. if len(b) != len(y):
  184. logging.error("Issue with Cts_ranks")
  185. p = False
  186. if len(c) != len(z):
  187. logging.error("Issue with Id2alias")
  188. p = False
  189. if p:
  190. logging.info("Database ok - no repeat rows added.")
  191. except sql.Error:
  192. logging.exception("encountered sql error in function 'duplicate test'.")
  193. if __name__ == "__main__":
  194. ap = argparse.ArgumentParser()
  195. ap.add_argument('db')
  196. ap.add_argument('src')
  197. ap.add_argument('-t', '--test', action='store_true')
  198. ap.add_argument('-q', '--sql', action='store_true')
  199. args = ap.parse_args()
  200. log_file = init_logging()
  201. print("Writing log to ", log_file)
  202. if args.test:
  203. duplicatestest(args.db, args.src)
  204. if args.sql:
  205. f(args.db, args.src)
  206. else:
  207. i(args.db, args.src)