Browse Source

Merge pull request #3951 from SomberNight/file_import_export_unification

File import-export unification
3.1
ThomasV 7 years ago
committed by GitHub
parent
commit
18ba4319da
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 18
      gui/qt/contact_list.py
  2. 18
      gui/qt/invoice_list.py
  3. 45
      gui/qt/main_window.py
  4. 37
      gui/qt/util.py
  5. 23
      lib/contacts.py
  6. 32
      lib/paymentrequest.py
  7. 37
      lib/util.py

18
gui/qt/contact_list.py

@ -26,13 +26,13 @@ import webbrowser
from electrum.i18n import _ from electrum.i18n import _
from electrum.bitcoin import is_address from electrum.bitcoin import is_address
from electrum.util import block_explorer_URL, FileImportFailed from electrum.util import block_explorer_URL
from electrum.plugins import run_hook from electrum.plugins import run_hook
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import ( from PyQt5.QtWidgets import (
QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem) QAbstractItemView, QFileDialog, QMenu, QTreeWidgetItem)
from .util import MyTreeWidget from .util import MyTreeWidget, import_meta_gui, export_meta_gui
class ContactList(MyTreeWidget): class ContactList(MyTreeWidget):
@ -53,15 +53,10 @@ class ContactList(MyTreeWidget):
self.parent.set_contact(item.text(0), item.text(1)) self.parent.set_contact(item.text(0), item.text(1))
def import_contacts(self): def import_contacts(self):
wallet_folder = self.parent.get_wallet_folder() import_meta_gui(self.parent, _('contacts'), self.parent.contacts.import_file, self.on_update)
filename, __ = QFileDialog.getOpenFileName(self.parent, "Select your wallet file", wallet_folder)
if not filename: def export_contacts(self):
return export_meta_gui(self.parent, _('contacts'), self.parent.contacts.export_file)
try:
self.parent.contacts.import_file(filename)
except FileImportFailed as e:
self.parent.show_message(str(e))
self.on_update()
def create_menu(self, position): def create_menu(self, position):
menu = QMenu() menu = QMenu()
@ -69,6 +64,7 @@ class ContactList(MyTreeWidget):
if not selected: if not selected:
menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog()) menu.addAction(_("New contact"), lambda: self.parent.new_contact_dialog())
menu.addAction(_("Import file"), lambda: self.import_contacts()) menu.addAction(_("Import file"), lambda: self.import_contacts())
menu.addAction(_("Export file"), lambda: self.export_contacts())
else: else:
names = [item.text(0) for item in selected] names = [item.text(0) for item in selected]
keys = [item.text(1) for item in selected] keys = [item.text(1) for item in selected]

18
gui/qt/invoice_list.py

@ -23,9 +23,10 @@
# CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE # CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
# SOFTWARE. # SOFTWARE.
from .util import *
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import format_time, FileImportFailed from electrum.util import format_time
from .util import *
class InvoiceList(MyTreeWidget): class InvoiceList(MyTreeWidget):
@ -57,15 +58,10 @@ class InvoiceList(MyTreeWidget):
self.parent.invoices_label.setVisible(len(inv_list)) self.parent.invoices_label.setVisible(len(inv_list))
def import_invoices(self): def import_invoices(self):
wallet_folder = self.parent.get_wallet_folder() import_meta_gui(self.parent, _('invoices'), self.parent.invoices.import_file, self.on_update)
filename, __ = QFileDialog.getOpenFileName(self.parent, "Select your wallet file", wallet_folder)
if not filename: def export_invoices(self):
return export_meta_gui(self.parent, _('invoices'), self.parent.invoices.export_file)
try:
self.parent.invoices.import_file(filename)
except FileImportFailed as e:
self.parent.show_message(str(e))
self.on_update()
def create_menu(self, position): def create_menu(self, position):
menu = QMenu() menu = QMenu()

45
gui/qt/main_window.py

@ -39,15 +39,14 @@ import PyQt5.QtCore as QtCore
from .exception_window import Exception_Hook from .exception_window import Exception_Hook
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from electrum.util import bh2u, bfh
from electrum import keystore, simple_config from electrum import keystore, simple_config
from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS, NetworkConstants from electrum.bitcoin import COIN, is_address, TYPE_ADDRESS, NetworkConstants
from electrum.plugins import run_hook from electrum.plugins import run_hook
from electrum.i18n import _ from electrum.i18n import _
from electrum.util import (format_time, format_satoshis, PrintError, from electrum.util import (format_time, format_satoshis, PrintError,
format_satoshis_plain, NotEnoughFunds, format_satoshis_plain, NotEnoughFunds,
UserCancelled, NoDynamicFeeEstimates) UserCancelled, NoDynamicFeeEstimates, profiler,
export_meta, import_meta, bh2u, bfh)
from electrum import Transaction from electrum import Transaction
from electrum import util, bitcoin, commands, coinchooser from electrum import util, bitcoin, commands, coinchooser
from electrum import paymentrequest from electrum import paymentrequest
@ -58,10 +57,8 @@ from .qrcodewidget import QRCodeWidget, QRDialog
from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit from .qrtextedit import ShowQRTextEdit, ScanQRTextEdit
from .transaction_dialog import show_transaction from .transaction_dialog import show_transaction
from .fee_slider import FeeSlider from .fee_slider import FeeSlider
from .util import * from .util import *
from electrum.util import profiler
class StatusBarButton(QPushButton): class StatusBarButton(QPushButton):
def __init__(self, icon, tooltip, func): def __init__(self, icon, tooltip, func):
@ -484,8 +481,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
contacts_menu = wallet_menu.addMenu(_("Contacts")) contacts_menu = wallet_menu.addMenu(_("Contacts"))
contacts_menu.addAction(_("&New"), self.new_contact_dialog) contacts_menu.addAction(_("&New"), self.new_contact_dialog)
contacts_menu.addAction(_("Import"), lambda: self.contact_list.import_contacts()) contacts_menu.addAction(_("Import"), lambda: self.contact_list.import_contacts())
contacts_menu.addAction(_("Export"), lambda: self.contact_list.export_contacts())
invoices_menu = wallet_menu.addMenu(_("Invoices")) invoices_menu = wallet_menu.addMenu(_("Invoices"))
invoices_menu.addAction(_("Import"), lambda: self.invoice_list.import_invoices()) invoices_menu.addAction(_("Import"), lambda: self.invoice_list.import_invoices())
invoices_menu.addAction(_("Export"), lambda: self.invoice_list.export_invoices())
wallet_menu.addSeparator() wallet_menu.addSeparator()
wallet_menu.addAction(_("Find"), self.toggle_search).setShortcut(QKeySequence("Ctrl+F")) wallet_menu.addAction(_("Find"), self.toggle_search).setShortcut(QKeySequence("Ctrl+F"))
@ -2417,29 +2416,23 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, PrintError):
f.write(json.dumps(pklist, indent = 4)) f.write(json.dumps(pklist, indent = 4))
def do_import_labels(self): def do_import_labels(self):
labelsFile = self.getOpenFileName(_("Open labels file"), "*.json") def import_labels(path):
if not labelsFile: return def _validate(data):
try: return data # TODO
with open(labelsFile, 'r') as f:
data = f.read() def import_labels_assign(data):
for key, value in json.loads(data).items(): for key, value in data.items():
self.wallet.set_label(key, value) self.wallet.set_label(key, value)
self.show_message(_("Your labels were imported from") + " '%s'" % str(labelsFile)) import_meta(path, _validate, import_labels_assign)
except (IOError, os.error) as reason:
self.show_critical(_("Electrum was unable to import your labels.") + "\n" + str(reason)) def on_import():
self.address_list.update() self.need_update.set()
self.history_list.update() import_meta_gui(self, _('labels'), import_labels, on_import)
def do_export_labels(self): def do_export_labels(self):
labels = self.wallet.labels def export_labels(filename):
try: export_meta(self.wallet.labels, filename)
fileName = self.getSaveFileName(_("Select file to save your labels"), 'electrum_labels.json', "*.json") export_meta_gui(self, _('labels'), export_labels)
if fileName:
with open(fileName, 'w+') as f:
json.dump(labels, f, indent=4, sort_keys=True)
self.show_message(_("Your labels were exported to") + " '%s'" % str(fileName))
except (IOError, os.error) as reason:
self.show_critical(_("Electrum was unable to export your labels.") + "\n" + str(reason))
def sweep_key_dialog(self): def sweep_key_dialog(self):
d = WindowModalDialog(self, title=_('Sweep private keys')) d = WindowModalDialog(self, title=_('Sweep private keys'))

37
gui/qt/util.py

@ -6,11 +6,15 @@ import queue
from collections import namedtuple from collections import namedtuple
from functools import partial from functools import partial
from electrum.i18n import _
from PyQt5.QtGui import * from PyQt5.QtGui import *
from PyQt5.QtCore import * from PyQt5.QtCore import *
from PyQt5.QtWidgets import * from PyQt5.QtWidgets import *
from electrum.i18n import _
from electrum.util import FileImportFailed, FileExportFailed
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
if platform.system() == 'Windows': if platform.system() == 'Windows':
MONOSPACE_FONT = 'Lucida Console' MONOSPACE_FONT = 'Lucida Console'
elif platform.system() == 'Darwin': elif platform.system() == 'Darwin':
@ -21,8 +25,6 @@ else:
dialogs = [] dialogs = []
from electrum.paymentrequest import PR_UNPAID, PR_PAID, PR_EXPIRED
pr_icons = { pr_icons = {
PR_UNPAID:":icons/unpaid.png", PR_UNPAID:":icons/unpaid.png",
PR_PAID:":icons/confirmed.png", PR_PAID:":icons/confirmed.png",
@ -675,6 +677,35 @@ class AcceptFileDragDrop:
raise NotImplementedError() raise NotImplementedError()
def import_meta_gui(electrum_window, title, importer, on_success):
filter_ = "JSON (*.json);;All files (*)"
filename = electrum_window.getOpenFileName(_("Open {} file").format(title), filter_)
if not filename:
return
try:
importer(filename)
except FileImportFailed as e:
electrum_window.show_critical(str(e))
else:
electrum_window.show_message(_("Your {} were successfully imported").format(title))
on_success()
def export_meta_gui(electrum_window, title, exporter):
filter_ = "JSON (*.json);;All files (*)"
filename = electrum_window.getSaveFileName(_("Select file to save your {}").format(title),
'electrum_{}.json'.format(title), filter_)
if not filename:
return
try:
exporter(filename)
except FileExportFailed as e:
electrum_window.show_critical(str(e))
else:
electrum_window.show_message(_("Your {0} were exported to '{1}'")
.format(title, str(filename)))
if __name__ == "__main__": if __name__ == "__main__":
app = QApplication([]) app = QApplication([])
t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done")) t = WaitingDialog(None, 'testing ...', lambda: [time.sleep(1)], lambda x: QMessageBox.information(None, 'done', "done"))

23
lib/contacts.py

@ -28,7 +28,7 @@ import sys
from . import bitcoin from . import bitcoin
from . import dnssec from . import dnssec
from .util import FileImportFailed, FileImportFailedEncrypted from .util import export_meta, import_meta
class Contacts(dict): class Contacts(dict):
@ -51,18 +51,15 @@ class Contacts(dict):
self.storage.put('contacts', dict(self)) self.storage.put('contacts', dict(self))
def import_file(self, path): def import_file(self, path):
try: import_meta(path, self._validate, self.load_meta)
with open(path, 'r') as f:
d = self._validate(json.loads(f.read())) def load_meta(self, data):
except json.decoder.JSONDecodeError: self.update(data)
traceback.print_exc(file=sys.stderr)
raise FileImportFailedEncrypted()
except BaseException:
traceback.print_exc(file=sys.stdout)
raise FileImportFailed()
self.update(d)
self.save() self.save()
def export_file(self, filename):
export_meta(self, filename)
def __setitem__(self, key, value): def __setitem__(self, key, value):
dict.__setitem__(self, key, value) dict.__setitem__(self, key, value)
self.save() self.save()
@ -120,13 +117,13 @@ class Contacts(dict):
return None return None
def _validate(self, data): def _validate(self, data):
for k,v in list(data.items()): for k, v in list(data.items()):
if k == 'contacts': if k == 'contacts':
return self._validate(v) return self._validate(v)
if not bitcoin.is_address(k): if not bitcoin.is_address(k):
data.pop(k) data.pop(k)
else: else:
_type,_ = v _type, _ = v
if _type != 'address': if _type != 'address':
data.pop(k) data.pop(k)
return data return data

32
lib/paymentrequest.py

@ -40,7 +40,7 @@ except ImportError:
from . import bitcoin from . import bitcoin
from . import util from . import util
from .util import print_error, bh2u, bfh from .util import print_error, bh2u, bfh
from .util import FileImportFailed, FileImportFailedEncrypted from .util import export_meta, import_meta
from . import transaction from . import transaction
from . import x509 from . import x509
from . import rsakey from . import rsakey
@ -468,27 +468,29 @@ class InvoiceStore(object):
continue continue
def import_file(self, path): def import_file(self, path):
try: def validate(data):
with open(path, 'r') as f: return data # TODO
d = json.loads(f.read()) import_meta(path, validate, self.on_import)
self.load(d)
except json.decoder.JSONDecodeError: def on_import(self, data):
traceback.print_exc(file=sys.stderr) self.load(data)
raise FileImportFailedEncrypted()
except BaseException:
traceback.print_exc(file=sys.stdout)
raise FileImportFailed()
self.save() self.save()
def save(self): def export_file(self, filename):
l = {} export_meta(self.dump(), filename)
def dump(self):
d = {}
for k, pr in self.invoices.items(): for k, pr in self.invoices.items():
l[k] = { d[k] = {
'hex': bh2u(pr.raw), 'hex': bh2u(pr.raw),
'requestor': pr.requestor, 'requestor': pr.requestor,
'txid': pr.tx 'txid': pr.tx
} }
self.storage.put('invoices', l) return d
def save(self):
self.storage.put('invoices', self.dump())
def get_status(self, key): def get_status(self, key):
pr = self.get(key) pr = self.get(key)

37
lib/util.py

@ -59,15 +59,19 @@ class InvalidPassword(Exception):
class FileImportFailed(Exception): class FileImportFailed(Exception):
def __init__(self, message=''):
self.message = str(message)
def __str__(self): def __str__(self):
return _("Failed to import file.") return _("Failed to import from file.") + "\n" + self.message
class FileExportFailed(Exception):
def __init__(self, message=''):
self.message = str(message)
class FileImportFailedEncrypted(FileImportFailed):
def __str__(self): def __str__(self):
return (_('Failed to import file.') + ' ' + return _("Failed to export to file.") + "\n" + self.message
_('Perhaps it is encrypted...') + '\n' +
_('Importing encrypted files is not supported.'))
# Throw this exception to unwind the stack like when an error occurs. # Throw this exception to unwind the stack like when an error occurs.
@ -784,3 +788,26 @@ def setup_thread_excepthook():
def versiontuple(v): def versiontuple(v):
return tuple(map(int, (v.split(".")))) return tuple(map(int, (v.split("."))))
def import_meta(path, validater, load_meta):
try:
with open(path, 'r') as f:
d = validater(json.loads(f.read()))
load_meta(d)
#backwards compatibility for JSONDecodeError
except ValueError:
traceback.print_exc(file=sys.stderr)
raise FileImportFailed(_("Invalid JSON code."))
except BaseException as e:
traceback.print_exc(file=sys.stdout)
raise FileImportFailed(e)
def export_meta(meta, fileName):
try:
with open(fileName, 'w+') as f:
json.dump(meta, f, indent=4, sort_keys=True)
except (IOError, os.error) as e:
traceback.print_exc(file=sys.stderr)
raise FileExportFailed(e)

Loading…
Cancel
Save