Skip to content
This repository was archived by the owner on May 16, 2019. It is now read-only.

Commit 9a948f4

Browse files
committed
Added a backup tool to import/export store data
1 parent 7d2814b commit 9a948f4

File tree

2 files changed

+187
-0
lines changed

2 files changed

+187
-0
lines changed

api/restapi.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
__author__ = 'chris'
2+
import backuptool
23
import json
34
import os
45
from txrestapi.resource import APIResource
@@ -601,3 +602,29 @@ def upload_image(self, request):
601602
request.write(json.dumps({"success": False, "reason": e.message}, indent=4))
602603
request.finish()
603604
return server.NOT_DONE_YET
605+
606+
@POST('^/api/v1/backup_files')
607+
def backup_files(self, request):
608+
"""Archives OpenBazaar files in a single tar archive."""
609+
output = request.args["output"][0]
610+
return backuptool.backupfiles(output)
611+
612+
@POST('^/api/v1/export_database')
613+
def export_database(self, request):
614+
"""Exports given tables to the OpenBazaar folder."""
615+
tables_and_columns = request.args["tables_and_columns"][0]
616+
remove_previous = request.args["remove_previous"][0]
617+
return backuptool.exportdatabase(tables_and_columns, remove_previous)
618+
619+
@POST('^/api/v1/restore_files')
620+
def restore_files(self, request):
621+
"""Restores files of given archive to OpenBazaar folder."""
622+
input_file = request.args["input_file"][0]
623+
rem_db_files = request.args["rem_db_files"][0]
624+
return backuptool.restorefiles(input_file, rem_db_files)
625+
626+
@POST('^/api/v1/import_database')
627+
def import_database(self, request):
628+
"""Imports given CSV file to the database."""
629+
remove_previous = request.args["remove_previous"][0]
630+
return backuptool.importdatabase(remove_previous)

backuptool.py

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
"""Import and export data for the OpenBazaar server."""
2+
__author__ = 'marc'
3+
from constants import DATA_FOLDER
4+
import csv
5+
import db.datastore as db
6+
import errno
7+
import os
8+
import re
9+
import shutil
10+
import sqlite3 as lite
11+
import tarfile
12+
import time
13+
14+
_TABLES = {
15+
'hashmap': ['hash', 'filepath'],
16+
'profile': ['id', 'serializedUserInfo'],
17+
'listings': ['id', 'serializedListings'],
18+
'keys': ['type', 'privkey', 'pubkey'],
19+
'followers': ['id', 'serializedFollowers'],
20+
'following': ['id', 'serializedFollowing'],
21+
'messages': [
22+
'guid', 'handle', 'signed_pubkey', 'encryption_pubkey', 'subject',
23+
'message_type', 'message', 'timestamp', 'avatar_hash', 'signature',
24+
'outgoing'
25+
],
26+
'notifications': ['guid', 'handle', 'message', 'timestamp', 'avatar_hash'],
27+
'vendors': ['guid', 'ip', 'port', 'signedPubkey'],
28+
'moderators': [
29+
'guid', 'signedPubkey', 'encryptionKey', 'encryptionSignature',
30+
'bitcoinKey', 'bitcoinSignature', 'handle'
31+
],
32+
'purchases': [
33+
'id', 'title', 'timestamp', 'btc', 'address', 'status', 'thumbnail',
34+
'seller', 'proofSig'
35+
],
36+
'sales': [
37+
'id', 'title', 'timestamp', 'btc', 'address', 'status', 'thumbnail',
38+
'seller'
39+
],
40+
'dht': ['keyword', 'id', 'value', 'birthday']
41+
}
42+
43+
def _getdatabase():
44+
"""Retrieves the OpenBazaar database file."""
45+
database = db.Database()
46+
return database.DATABASE
47+
48+
def silentremove(filename):
49+
"""Silently removes a file if it exists."""
50+
try:
51+
os.remove(filename)
52+
except OSError as err:
53+
if err.errno != errno.ENOENT: # ENOENT: no such file or directory
54+
raise
55+
56+
def backupfiles(output=None):
57+
"""Archives OpenBazaar files in a single tar archive."""
58+
os.chdir(DATA_FOLDER)
59+
60+
# Archive files
61+
files = os.listdir(DATA_FOLDER)
62+
if not output:
63+
output = 'backup_{0}.tar.gz'.format(time.strftime('%Y-%m-%d'))
64+
silentremove(output)
65+
with tarfile.open(output, 'w:gz') as tar:
66+
for fil in files:
67+
tar.add(fil)
68+
tar.close()
69+
return True
70+
71+
72+
def restorefiles(input_file, remove_previous_database_files=False):
73+
"""Restores files of given archive to OpenBazaar folder."""
74+
if not input_file:
75+
return 'Input path is needed'
76+
os.chdir(DATA_FOLDER)
77+
78+
# Remove existing database files if any
79+
if remove_previous_database_files and os.path.exists('backup'):
80+
shutil.rmtree('backup')
81+
82+
# Unarchive files
83+
with tarfile.open(input_file, 'r:gz') as tar:
84+
tar.extractall()
85+
86+
return True
87+
88+
def _exportdatabase_tocsv(tables_and_columns):
89+
"""Reads the database for all given tables and stores them as CSV files."""
90+
db_file = _getdatabase()
91+
with lite.connect(db_file) as db_connection:
92+
db_connection.text_factory = str
93+
cursor = db_connection.cursor()
94+
for table in tables_and_columns:
95+
table_name = table[0]
96+
table_columns = ', '.join(table[1])
97+
query = "SELECT {0} FROM {1}".format(table_columns, table_name)
98+
data = cursor.execute(query)
99+
file_name = 'table_{0}.csv'.format(table_name)
100+
file_path = os.path.join('backup', file_name)
101+
with open(file_path, 'wb') as fil:
102+
writer = csv.writer(fil)
103+
writer.writerow(table[1])
104+
writer.writerows(data)
105+
return True
106+
107+
def exportdatabase(table_list, remove_previous=False):
108+
"""Exports given tables to the OpenBazaar folder."""
109+
os.chdir(DATA_FOLDER)
110+
111+
# Parse table list
112+
table_list = table_list.replace(' ', '').split(',')
113+
tables_and_columns = []
114+
for table in table_list:
115+
if table in _TABLES:
116+
tables_and_columns.append((table, _TABLES[table]))
117+
else:
118+
return 'ERROR, Table not found: {0}'.format(table)
119+
120+
# Remove existing database files and re-make them
121+
if remove_previous and os.path.exists('backup'):
122+
shutil.rmtree('backup')
123+
if not os.path.exists('backup'):
124+
os.makedirs('backup')
125+
return _exportdatabase_tocsv(tables_and_columns)
126+
127+
def _importcsv_totable(file_name, delete_data_first=False):
128+
"""Imports given CSV file to the database."""
129+
table_name = re.search(r'table_(\w+).csv', file_name).group(1)
130+
db_file = _getdatabase()
131+
with lite.connect(db_file) as db_connection:
132+
db_connection.text_factory = str
133+
cursor = db_connection.cursor()
134+
if delete_data_first:
135+
cursor.execute('DELETE FROM {0}'.format(table_name))
136+
with open(file_name, 'rb') as fil:
137+
reader = csv.reader(fil)
138+
header = True
139+
for row in reader:
140+
if header:
141+
header = False
142+
columns = ', '.join(['?' for _ in row])
143+
insertsql = 'INSERT INTO {0} VALUES ({1})'.format(
144+
table_name, columns)
145+
rowlen = len(row)
146+
else:
147+
if len(row) == rowlen:
148+
cursor.execute(insertsql, row)
149+
150+
def importdatabase(delete_previous_data=False):
151+
"""Imports table files from the OpenBazaar folder."""
152+
os.chdir(DATA_FOLDER)
153+
154+
# Restore database files to the database
155+
result = True
156+
if os.path.exists('backup'):
157+
files = ['backup/{0}'.format(fil) for fil in os.listdir('backup')]
158+
for fil in files:
159+
result = result and _importcsv_totable(fil, delete_previous_data)
160+
return result

0 commit comments

Comments
 (0)