Refactored attachment code so it will match to the MVP pattern

This commit is contained in:
Manuel Cortez 2019-01-06 22:51:20 -06:00
parent 71ce8597e5
commit 589a7da53b
9 changed files with 257 additions and 4 deletions

View File

@ -6,6 +6,9 @@ import logging
import webbrowser
import arrow
import wx
import presenters
import views
import interactors
import languageHandler
import widgetUtils
from . import messages
@ -13,7 +16,6 @@ from presenters import player
import output
from . import selector
from . import posts
from . import attach
from pubsub import pub
from vk_api.exceptions import VkApiError
from vk_api import upload
@ -840,7 +842,7 @@ class chatBuffer(baseBuffer):
return retrieved
def add_attachment(self, *args, **kwargs):
a = attach.attach(self.session, True)
a = presenters.attachPresenter(session=self.session, view=views.attachDialog(voice_messages=True), interactor=interactors.attachInteractor())
if len(a.attachments) != 0:
self.attachments_to_be_sent = a.attachments

View File

@ -2,9 +2,11 @@
from __future__ import unicode_literals
import time
import widgetUtils
import presenters
import views
import interactors
import output
from pubsub import pub
from . import attach
from wxUI.dialogs import message, selector
from extra import SpellChecker, translator
from logging import getLogger
@ -71,7 +73,7 @@ class post(object):
checker.clean()
def show_attach_dialog(self, *args, **kwargs):
a = attach.attach(self.session)
a = presenters.attachPresenter(session=self.session, view=views.attachDialog(), interactor=interactors.attachInteractor())
if len(a.attachments) != 0:
self.attachments = a.attachments

View File

@ -1,3 +1,4 @@
from .attach import *
from . audioRecorder import *
from .configuration import *
from .profiles import *

74
src/interactors/attach.py Normal file
View File

@ -0,0 +1,74 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import widgetUtils
from pubsub import pub
from wxUI.dialogs import selector
from wxUI.menus import attachMenu
from . import base
class attachInteractor(base.baseInteractor):
def insert_attachment(self, attachment):
self.view.attachments.insert_item(False, *attachment)
def remove_attachment(self, attachment):
self.view.attachments.remove_item(attachment)
def install(self, *args, **kwargs):
super(attachInteractor, self).install(*args, **kwargs)
widgetUtils.connect_event(self.view.photo, widgetUtils.BUTTON_PRESSED, self.on_image)
widgetUtils.connect_event(self.view.audio, widgetUtils.BUTTON_PRESSED, self.on_audio)
if hasattr(self.view, "voice_message"):
widgetUtils.connect_event(self.view.voice_message, widgetUtils.BUTTON_PRESSED, self.on_upload_voice_message)
widgetUtils.connect_event(self.view.remove, widgetUtils.BUTTON_PRESSED, self.on_remove_attachment)
pub.subscribe(self.insert_attachment, self.modulename+"_insert_attachment")
pub.subscribe(self.remove_attachment, self.modulename+"_remove_attachment")
def uninstall(self):
super(attachInteractor, self).uninstall()
pub.unsubscribe(self.insert_attachment, self.modulename+"_insert_attachment")
pub.unsubscribe(self.remove_attachment, self.modulename+"_remove_attachment")
def on_image(self, *args, **kwargs):
""" display menu for adding image attachments. """
m = attachMenu()
# disable add from VK as it is not supported in images, yet.
m.add.Enable(False)
widgetUtils.connect_event(m, widgetUtils.MENU, self.on_upload_image, menuitem=m.upload)
self.view.PopupMenu(m, self.view.photo.GetPosition())
def on_audio(self, *args, **kwargs):
""" display menu to add audio attachments."""
m = attachMenu()
widgetUtils.connect_event(m, widgetUtils.MENU, self.on_upload_audio, menuitem=m.upload)
widgetUtils.connect_event(m, widgetUtils.MENU, self.on_add_audio, menuitem=m.add)
self.view.PopupMenu(m, self.view.audio.GetPosition())
def on_upload_image(self, *args, **kwargs):
""" allows uploading an image from the computer.
"""
image, description = self.view.get_image()
if image != None:
self.presenter.upload_image(image, description)
def on_upload_audio(self, *args, **kwargs):
""" Allows uploading an audio file from the computer. Only mp3 files are supported. """
audio = self.view.get_audio()
if audio != None:
self.presenter.upload_audio(audio)
def on_upload_voice_message(self, *args, **kwargs):
self.presenter.upload_voice_message()
def on_add_audio(self, *args, **kwargs):
""" Allow adding an audio directly from the user's audio library."""
audios = self.presenter.get_available_audios()
select = selector.selectAttachment(_("Select the audio files you want to send"), audios)
if select.get_response() == widgetUtils.OK and select.attachments.GetCount() > 0:
attachments = select.get_all_attachments()
self.presenter.take_audios(attachments)
def on_remove_attachment(self, *args, **kwargs):
""" Remove the currently focused item from the attachments list."""
current_item = self.view.attachments.get_selected()
self.presenter.remove_attachment(current_item)

View File

@ -1,4 +1,5 @@
# -*- coding: utf-8 -*-
from .attach import *
from .audioRecorder import *
from .configuration import *
from .profiles import *

108
src/presenters/attach.py Normal file
View File

@ -0,0 +1,108 @@
# -*- coding: utf-8 -*-
""" Attachment controller for different kind of posts in VK.
this controller will take care of preparing data structures to be uploaded later, when the user decides to start the upload process by sending the post.
"""
from __future__ import unicode_literals
import os
import logging
import interactors
import views
from mutagen.id3 import ID3
from sessionmanager.utils import seconds_to_string
from . import audioRecorder, base
log = logging.getLogger(__file__)
class attachPresenter(base.basePresenter):
""" Controller used in some sections of the application, it can do the following:
* Handle all user input related to adding local or online files (online files are those already uploaded in vk).
* Prepare local files to be uploaded once a post will be sent (no uploading work is done here, but structured dicts will be generated).
* Parse online files and allow addition of them as attachments, so this controller will add both local and online files in the same dialog.
"""
def __init__(self, session, view, interactor, voice_messages=False):
""" Constructor.
@ session sessionmanager.session object: an object capable of calling all VK methods and accessing the session database.
@voice_messages bool: If True, will add a button for sending voice messages. Functionality for this button has not been added yet.
"""
super(attachPresenter, self).__init__(view=view, interactor=interactor, modulename="attach")
self.session = session
# Self.attachments will hold a reference to all attachments added to the dialog.
self.attachments = list()
self.run()
def upload_image(self, image, description):
""" allows uploading an image from the computer.
"""
imageInfo = {"type": "photo", "file": image, "description": description, "from": "local"}
self.attachments.append(imageInfo)
# Translators: This is the text displayed in the attachments dialog, when the user adds a photo.
info = [_("Photo"), description]
self.send_message("insert_attachment", attachment=info)
self.send_message("enable_control", control="remove")
def upload_audio(self, audio):
""" Allows uploading an audio file from the computer. Only mp3 files are supported. """
if audio != None:
# Define data structure for this attachment, as will be required by VK API later.
# Let's extract the ID3 tags to show them in the list and send them to VK, too.
audio_tags = ID3(audio)
if "TIT2" in audio_tags:
title = audio_tags["TIT2"].text[0]
else:
title = _("Untitled")
if "TPE1" in audio_tags:
artist = audio_tags["TPE1"].text[0]
else:
artist = _("Unknown artist")
audioInfo = {"type": "audio", "file": audio, "from": "local", "title": title, "artist": artist}
self.attachments.append(audioInfo)
# Translators: This is the text displayed in the attachments dialog, when the user adds an audio file.
info = [_("Audio file"), "{title} - {artist}".format(title=title, artist=artist)]
self.send_message("insert_attachment", attachment=info)
self.send_message("enable_control", control="remove")
def upload_voice_message(self):
a = audioRecorder.audioRecorderPresenter(view=views.audioRecorderDialog(), interactor=interactors.audioRecorderInteractor())
if a.file != None and a.duration != 0:
audioInfo = {"type": "voice_message", "file": a.file, "from": "local"}
self.attachments.append(audioInfo)
# Translators: This is the text displayed in the attachments dialog, when the user adds an audio file.
info = [_("Voice message"), seconds_to_string(a.duration,)]
self.send_message("insert_attachment", attachment=info)
self.send_message("enable_control", control="remove")
####### ToDo: replace this with selector presenter when finished.
def get_available_audios(self):
# Let's reuse the already downloaded audios.
list_of_audios = self.session.db["me_audio"]["items"]
audios = []
for i in list_of_audios:
audios.append("{0}, {1}".format(i["title"], i["artist"]))
return audios
def take_audios(self, audios_list):
list_of_audios = self.session.db["me_audio"]["items"]
for i in audios_list:
info = dict(type="audio", id=list_of_audios[i]["id"], owner_id=list_of_audios[i]["owner_id"])
info["from"] = "online"
self.attachments.append(info)
# Translators: This is the text displayed in the attachments dialog, when the user adds an audio file.
info2 = [_("Audio file"), "{0} - {1}".format(list_of_audios[i]["title"], list_of_audios[i]["artist"])]
self.send_message("insert_attachment", attachment=info2)
self.check_remove_status()
def remove_attachment(self, item_index):
""" Remove the currently focused item from the attachments list."""
log.debug("Removing item %d" % (item_index,))
if item_index == -1: item_index = 0
self.attachments.pop(item_index)
self.send_message("remove_attachment", attachment=item_index)
self.check_remove_status()
log.debug("Removed")
def check_remove_status(self):
""" Checks whether the remove button should remain enabled."""
if len(self.attachments) == 0:
self.send_message("disable_control", control="remove")

View File

@ -16,6 +16,7 @@ class audioRecorderPresenter(base.basePresenter):
self.recording = None
self.duration = 0
self.playing = None
self.file = None
self.run()
def toggle_recording(self, *args, **kwargs):

View File

@ -1,3 +1,9 @@
""" Views package for socializer. A view is the Graphical user interface in the application. This package includes everything needed to render a window.
No other functionality is assumed in this package.
All modules from here should be used via an interactor (read description of the interactors package).
Interactors will be routing user events (like buttons pressed, menus activated and so on) to presenter functions.
"""
from .dialogs.attach import *
from .dialogs.audioRecorder import *
from .dialogs.configuration import *
from .dialogs.profiles import *

View File

@ -0,0 +1,58 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import wx
import widgetUtils
class attachDialog(widgetUtils.BaseDialog):
def __init__(self, voice_messages=False):
super(attachDialog, self).__init__(None, title=_("Add an attachment"))
panel = wx.Panel(self)
sizer = wx.BoxSizer(wx.VERTICAL)
lbl1 = wx.StaticText(panel, wx.NewId(), _("Attachments"))
self.attachments = widgetUtils.list(panel, _("Type"), _("Title"), style=wx.LC_REPORT)
box = wx.BoxSizer(wx.HORIZONTAL)
box.Add(lbl1, 0, wx.ALL, 5)
box.Add(self.attachments.list, 0, wx.ALL, 5)
sizer.Add(box, 0, wx.ALL, 5)
static = wx.StaticBox(panel, label=_("Add attachments"))
self.photo = wx.Button(panel, wx.NewId(), _("&Photo"))
self.audio = wx.Button(panel, wx.NewId(), _("Audio file"))
if voice_messages:
self.voice_message = wx.Button(panel, wx.NewId(), _("Voice message"))
self.remove = wx.Button(panel, wx.NewId(), _("Remove attachment"))
self.remove.Enable(False)
btnsizer = wx.StaticBoxSizer(static, wx.HORIZONTAL)
btnsizer.Add(self.photo, 0, wx.ALL, 5)
btnsizer.Add(self.audio, 0, wx.ALL, 5)
if voice_messages:
btnsizer.Add(self.voice_message, 0, wx.ALL, 5)
sizer.Add(btnsizer, 0, wx.ALL, 5)
ok = wx.Button(panel, wx.ID_OK)
ok.SetDefault()
cancelBtn = wx.Button(panel, wx.ID_CANCEL)
btnSizer = wx.BoxSizer()
btnSizer.Add(ok, 0, wx.ALL, 5)
btnSizer.Add(cancelBtn, 0, wx.ALL, 5)
sizer.Add(btnSizer, 0, wx.ALL, 5)
panel.SetSizer(sizer)
self.SetClientSize(sizer.CalcMin())
def get_image(self):
openFileDialog = wx.FileDialog(self, _("Select the picture to be uploaded"), "", "", _("Image files (*.png, *.jpg, *.gif)|*.png; *.jpg; *.gif"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return None
dsc = self.ask_description()
return (openFileDialog.GetPath(), dsc)
def ask_description(self):
dlg = wx.TextEntryDialog(self, _("please provide a description"), _("Description"))
dlg.ShowModal()
result = dlg.GetValue()
dlg.Destroy()
return result
def get_audio(self):
openFileDialog = wx.FileDialog(self, _("Select the audio file to be uploaded"), "", "", _("Audio files (*.mp3)|*.mp3"), wx.FD_OPEN | wx.FD_FILE_MUST_EXIST)
if openFileDialog.ShowModal() == wx.ID_CANCEL:
return None
return openFileDialog.GetPath()