Compare commits

...

46 Commits

Author SHA1 Message Date
97afc379e8 Pushed a new snapshot 2021-06-28 00:47:23 -05:00
7f401ba789 Removed unneeded locales 2021-06-28 00:39:45 -05:00
ff22ae5653 Merge branch 'next-gen' of https://github.com/manuelcortez/twblue into next-gen 2021-06-28 00:38:54 -05:00
02d94fcea0 Fixed a small issue when loading a conversation buffer 2021-06-28 00:36:53 -05:00
df2015f360 Merge pull request #384 from manuelcortez/twitter_videos
Support for Twitter video playback
2021-06-27 23:40:11 -05:00
64a14c831b Added initial support for playback of Twitter videos (only works in tweets so far) 2021-06-27 21:40:22 -05:00
fbbe7852c2 Added serbian to generate documentation 2021-06-27 20:57:13 -05:00
9dfccd2bd0 Updated documentation generators 2021-06-27 20:55:16 -05:00
969a75e9f3 Merge pull request #380 from manuelcortez/better_memory_management
Replace the cache database for a SQLite backed implementation
2021-06-27 19:10:50 -05:00
a59ba5ef78 Reset cache every time we call to save_persistent_data in tests 2021-06-27 19:09:49 -05:00
3ebfdbc48b Request for a restart when switching memory cache 2021-06-27 19:09:21 -05:00
8db14a95c1 Call reduce.reduce_tweet and session.save_users when retrieving previous items for a buffer 2021-06-27 18:07:56 -05:00
1b9062d86f aHandles deleted users for direct messages without wasting too many API calls 2021-06-27 18:05:35 -05:00
4b60a79e49 Made get_all_mentioned to take into account sometimes tweets might have no entities defined 2021-06-27 18:04:26 -05:00
002e1ccb55 If get_user is called with a full user object as an argument, logs it and changes the object to be only the user_id 2021-06-27 16:03:36 -05:00
0bcdf88290 Merge pull request #382 from nidza07/SerbianTranslation
Added Serbian documentation and changelog.
2021-06-27 02:35:55 -05:00
0612c653b8 Make TWBlue to respect persist_size when loading the cache database. Added tests for both reading from disk and loading in memory caches 2021-06-27 02:32:28 -05:00
nikola nikola
c5dadb063a Added Serbian documentation and changelog. 2021-06-27 04:22:02 +02:00
35d6010298 Save tweets cache by taking into account persist_size 2021-06-26 15:16:50 -05:00
40a63d9e16 Merge pull request #381 from nidza07/SerbianTranslation
Updated Serbian translation.
2021-06-26 02:16:13 -05:00
5712dd735b Added a new setting in account dialog to control whehter we load the cache db into memory or read it from disk 2021-06-26 01:52:07 -05:00
2c75ea5005 Fixed a small issue referencing an user in the old way while retrieving all mentioned users in a tweet 2021-06-26 01:50:47 -05:00
e35f37fcc2 Updated changelog 2021-06-26 01:49:55 -05:00
71358ea74d changed function names and cleaned code a little bit to reflect better the changes to object percistance 2021-06-25 23:35:33 -05:00
b8f822830f Added proper docstrings to reduce twitter objects 2021-06-25 22:47:10 -05:00
74e4fe6357 Ensure direct message buffers are correctly saved in database 2021-06-25 16:49:23 -05:00
77bee64421 Pass a null value to tweepy.models.Status as sometimes database saved objects might not include it 2021-06-25 16:48:44 -05:00
c761230566 Reduce the size of all tweets so it might make easier to handle those in a realtime database 2021-06-25 16:25:51 -05:00
49505fabcd Modify utils so those will take into account that entities might be not present in tweet objects 2021-06-25 13:14:01 -05:00
4ad01d7833 Retrieve user objects from the users list stored in SqlDict as opposed to loading it from tweet objects 2021-06-25 13:13:00 -05:00
ab1a13f886 Improve save_users() and get_user() as those will be used in more places later 2021-06-25 13:11:33 -05:00
nikola nikola
44c25e54f8 Updated Serbian translation. 2021-06-25 18:33:16 +02:00
cdabd6f055 Merge branch 'next-gen' into better_memory_management 2021-06-24 09:54:45 -05:00
60144a6b08 Added initial support to SqliteDict package 2021-06-24 09:52:10 -05:00
382acf7c8c Use slitedict to attempt to reduce memory usage when caching tweets 2021-06-23 13:40:21 -05:00
José Manuel Delicado
03ba59028f Merge pull request #379 from ivnc/next-gen
Fix arrow for Galician
2021-06-23 20:05:24 +02:00
Iván Novegil
50125fc55a Fix arrow for Galician. Modify some existing translations to localize them properly 2021-06-23 18:45:33 +02:00
39e1fb017c Made code indentation to comply with PEP8 2021-06-16 16:18:41 -05:00
2aaa4eced3 Removed Catala and Basque locale as they are in arrow already. Disabled Galician locale cause it's not fully implemented and fails 2021-06-16 16:17:16 -05:00
José Manuel
6d2eac5b1c Merge pull request #332 from Oreonan/fr-04062020
Some changes for french translation
2021-05-29 21:56:33 +02:00
Oreonan
40040d1b17 Merge branch 'next-gen' into fr-04062020 2021-05-14 21:24:46 +02:00
2a791d43bf Fixed an error when parsing a DM sent from an deleted account 2021-05-14 09:52:19 -05:00
b10aeb046d Changed label of direct message's text field so it will not reference any username in the hint. Closes #366 2021-05-07 17:18:21 -05:00
7d6e230fd9 Fix issue that avoids TWBlue to use Shift+F10 as menu key. Fixes #353 2021-05-07 16:52:10 -05:00
9346bba7a0 Fixed a small bug when sending long tweets via twyshort 2021-05-03 10:05:14 -05:00
Oreonan
0111c8aae1 some changes for french translation 2020-06-04 21:20:18 +02:00
170 changed files with 13330 additions and 25715 deletions

View File

@@ -39,5 +39,5 @@ florian Ionașcu
Christian Leo Mameli
Natalia Hedlund (Наталья Хедлунд)
Valeria (Валерия)
Corentin Bacqué-Cazenave
Oreonan
Artem Plaksin (maniyax)

View File

@@ -2,6 +2,14 @@
## changes in this version
* Added support for Twitter audio and videos: Tweets which contains audio or videos will be detected as audio items, and you can playback those with the regular command to play audios. ([#384,](https://github.com/manuelcortez/TWBlue/pull/384))
* We just implemented some changes in the way TWBlue handles tweets in order to reduce its RAM memory usage [#380](https://github.com/manuelcortez/TWBlue/pull/380):
* We reduced the tweets size by storing only the tweet fields we currently use. This should reduce tweet's size in memory for every object up to 75%.
* When using the cache database to store your tweets, there is a new setting present in the account settings dialog, in the general tab. This setting allows you to control whether TWBlue will load the whole database into memory (which is the current behaviour) or not.
* Loading the whole database into memory has the advantage of being extremely fast to access any element (for example when moving through tweets in a buffer), but it requires more memory as the tweet buffers grow up. This should, however, use less memory than before thanks to the optimizations performed in tweet objects. If you have a machine with enough memory, this should be a good option for your case.
* If you uncheck this setting, TWBlue will read the whole database from disk. This is significantly slower, but the advantage of this setting is that it will consume almost no extra memory, no matter how big is the tweets dataset. Be ware, though, that TWBlue might start to feel slower when accessing elements (for example when reading tweets) as the buffers grow up. This setting is suggested for computers with low memory or for those people not wanting to keep a really big amount of tweets stored.
* Changed the label in the direct message's text control so it will indicate that the user needs to write the text there, without referring to any username in particular. ([#366,](https://github.com/manuelcortez/TWBlue/issues/366))
* TWBlue will take Shift+F10 again as the contextual menu key in the list of items in a buffer. This stopped working after we have migrated to WX 4.1. ([#353,](https://github.com/manuelcortez/TWBlue/issues/353))
* TWBlue should render correctly retweets of quoted tweets. ([#365,](https://github.com/manuelcortez/TWBlue/issues/365))
* Fixed an error that was causing TWBlue to be unable to output to screen readers at times. ([#369,](https://github.com/manuelcortez/TWBlue/issues/369))
* Fixed autocomplete users feature. ([#367,](https://github.com/manuelcortez/TWBlue/issues/367))

View File

@@ -16,9 +16,9 @@ def prepare_documentation_in_file(fileSource, fileDest):
if "\n" == i:
newvar = "\"\","
elif "\n" == i[-1]:
newvar = "_(u\"\"\"%s\"\"\"),\n" % (i[:-1])
newvar = "\"\"\"%s\"\"\",\n" % (i[:-1])
else:
newvar = "_(u\"\"\"%s\"\"\"),\n" % (i)
newvar = "\"\"\"%s\"\"\",\n" % (i)
f2.write(newvar)
f1.close()
f2.write("]")

View File

@@ -8,28 +8,27 @@ import shutil
from codecs import open as _open
from importlib import reload
def change_language(name, language):
global _
os.environ["lang"] = language
_ = gettext.install(name, os.path.join(paths.app_path(), "locales"))
def get_translation_function(name, language):
if language == "en":
return gettext.NullTranslations()
translation_function = gettext.translation(name, os.path.join(paths.app_path(), "locales"), languages=[language])
return translation_function
# the list of supported language codes of TW Blue
languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro", "eu", "ca", "da"]
languages = ["en", "es", "fr", "de", "it", "gl", "ja", "ru", "ro", "eu", "ca", "da", "sr"]
def generate_document(language, document_type="documentation"):
if document_type == "documentation":
translation_file = "twblue-documentation"
change_language(translation_file, language)
reload(strings)
markdown_file = markdown.markdown("\n".join(strings.documentation[1:]), extensions=["markdown.extensions.toc"])
title = strings.documentation[0]
translation_function = get_translation_function(translation_file, language)
markdown_file = markdown.markdown("\n".join([translation_function.gettext(s[:-1]) if s != "\n" else s for s in strings.documentation[1:]]), extensions=["markdown.extensions.toc"])
title = translation_function.gettext(strings.documentation[0][:-1])
filename = "manual.html"
elif document_type == "changelog":
translation_file = "twblue-changelog"
change_language(translation_file, language)
reload(changelog)
markdown_file = markdown.markdown("\n".join(changelog.documentation[1:]), extensions=["markdown.extensions.toc"])
title = changelog.documentation[0]
translation_function = get_translation_function(translation_file, language)
markdown_file = markdown.markdown("\n".join([translation_function.gettext(s[:-1]) if s != "\n" else s for s in changelog.documentation[1:]]), extensions=["markdown.extensions.toc"])
title = translation_function.gettext(changelog.documentation[0][:-1])
filename = "changelog.html"
first_html_block = """<!doctype html>
<html lang="%s">
@@ -56,11 +55,13 @@ def create_documentation():
shutil.copy(os.path.join("..", "license.txt"), os.path.join("documentation", "license.txt"))
for i in languages:
print("Creating documentation for: %s" % (i,))
try:
generate_document(i)
generate_document(i, "changelog")
except:
continue
print("Done")
change_language("twblue-documentation", "en")
import strings
import changelog
create_documentation()

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@@ -31,6 +31,7 @@ cx_freeze
tweepy
twitter-text-parser
pyenchant
sqlitedict
git+https://github.com/accessibleapps/libloader
git+https://github.com/accessibleapps/platform_utils
git+https://github.com/accessibleapps/accessible_output2

View File

@@ -12,6 +12,7 @@ reverse_timelines = boolean(default=False)
announce_stream_status = boolean(default=True)
retweet_mode = string(default="ask")
persist_size = integer(default=0)
load_cache_in_memory=boolean(default=True)
show_screen_names = boolean(default=False)
buffer_order = list(default=list('home','mentions', 'dm', 'sent_dm', 'sent_tweets','favorites','followers','friends','blocks','muted','events'))

View File

@@ -9,14 +9,14 @@ if snapshot == False:
update_url = 'https://twblue.es/updates/stable.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/stable.json'
else:
version = "5"
version = "6"
update_url = 'https://twblue.es/updates/snapshot.php'
mirror_update_url = 'https://raw.githubusercontent.com/manuelcortez/TWBlue/next-gen/updates/snapshots.json'
authors = ["Manuel Cortéz", "José Manuel Delicado"]
authorEmail = "manuel@manuelcortez.net"
copyright = "Copyright (C) 2013-2021, Manuel cortéz."
description = name+" is an app designed to use Twitter simply and efficiently while using minimal system resources. This app provides access to most Twitter features."
translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Rémy Ruiz (French)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"]
translators = ["Manuel Cortéz (English)", "Mohammed Al Shara, Hatoun Felemban (Arabic)", "Francisco Torres (Catalan)", "Manuel cortéz (Spanish)", "Sukil Etxenike Arizaleta (Basque)", "Jani Kinnunen (finnish)", "Oreonan (Français)", "Juan Buño (Galician)", "Steffen Schultz (German)", "Zvonimir Stanečić (Croatian)", "Robert Osztolykan (Hungarian)", "Christian Leo Mameli (Italian)", "Riku (Japanese)", "Paweł Masarczyk (Polish)", "Odenilton Júnior Santos (Portuguese)", "Florian Ionașcu, Nicușor Untilă (Romanian)", "Natalia Hedlund, Valeria Kuznetsova (Russian)", "Aleksandar Đurić (Serbian)", "Burak Yüksek (Turkish)"]
url = u"https://twblue.es"
report_bugs_url = "https://github.com/manuelcortez/twblue/issues"
supported_languages = []

View File

@@ -59,11 +59,17 @@ class buffer(object):
elif ev.GetKeyCode() == wx.WXK_F6: event = "volume_up"
elif ev.GetKeyCode() == wx.WXK_DELETE and ev.ShiftDown(): event = "clear_list"
elif ev.GetKeyCode() == wx.WXK_DELETE: event = "destroy_status"
# Raise a Special event when pressed Shift+F10 because Wx==4.1.x does not seems to trigger this by itself.
# See https://github.com/manuelcortez/TWBlue/issues/353
elif ev.GetKeyCode() == wx.WXK_F10 and ev.ShiftDown(): event = "show_menu"
else:
event = None
ev.Skip()
if event != None:
try:
### ToDo: Remove after WX fixes issue #353 in the widgets.
if event == "show_menu":
return self.show_menu(widgetUtils.MENU, pos=self.buffer.list.list.GetPosition())
getattr(self, event)()
except AttributeError:
pass

View File

@@ -19,7 +19,7 @@ import languageHandler
import logging
from audio_services import youtube_utils
from controller.buffers import baseBuffers
from sessions.twitter import compose, utils
from sessions.twitter import compose, utils, reduce
from mysc.thread_utils import call_threaded
from tweepy.error import TweepError
from tweepy.cursor import Cursor
@@ -178,7 +178,9 @@ class baseBufferController(baseBuffers.buffer):
val, cursor = val
if type(cursor) == tuple:
cursor = cursor[1]
self.session.db["cursors"][self.name] = cursor
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in val]
val = results
val.reverse()
@@ -190,7 +192,6 @@ class baseBufferController(baseBuffers.buffer):
return
number_of_items = self.session.order_buffer(self.name, val)
log.debug("Number of items retrieved: %d" % (number_of_items,))
self.put_items_on_list(number_of_items)
if hasattr(self, "finished_timeline") and self.finished_timeline == False:
if "-timeline" in self.name:
@@ -229,15 +230,19 @@ class baseBufferController(baseBuffers.buffer):
return
if items == None:
return
items_db = self.session.db[self.name]
self.session.add_users_from_results(items)
for i in items:
if utils.is_allowed(i, self.session.settings, self.name) == True and utils.find_item(i.id, self.session.db[self.name]) == None:
i = reduce.reduce_tweet(i)
i = self.session.check_quoted_status(i)
i = self.session.check_long_tweet(i)
elements.append(i)
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[self.name].insert(0, i)
items_db.insert(0, i)
else:
self.session.db[self.name].append(i)
items_db.append(i)
self.session.db[self.name] = items_db
selection = self.buffer.list.get_selected()
log.debug("Retrieved %d items from cursored search in function %s." % (len(elements), self.function))
if self.session.settings["general"]["reverse_timelines"] == False:
@@ -286,10 +291,12 @@ class baseBufferController(baseBuffers.buffer):
def remove_tweet(self, id):
if type(self.session.db[self.name]) == dict: return
for i in range(0, len(self.session.db[self.name])):
if self.session.db[self.name][i].id == id:
self.session.db[self.name].pop(i)
items = self.session.db[self.name]
for i in range(0, len(items)):
if items[i].id == id:
items.pop(i)
self.remove_item(i)
self.session.db[self.name] = items
def put_items_on_list(self, number_of_items):
list_to_use = self.session.db[self.name]
@@ -408,11 +415,12 @@ class baseBufferController(baseBuffers.buffer):
@_tweets_exist
def reply(self, *args, **kwargs):
tweet = self.get_right_tweet()
screen_name = tweet.user.screen_name
user = self.session.get_user(tweet.user)
screen_name = user.screen_name
id = tweet.id
twishort_enabled = hasattr(tweet, "twishort")
users = utils.get_all_mentioned(tweet, self.session.db, field="screen_name")
ids = utils.get_all_mentioned(tweet, self.session.db, field="id_str")
ids = utils.get_all_mentioned(tweet, self.session.db, field="id")
# Build the window title
if len(users) < 1:
title=_("Reply to {arg0}").format(arg0=screen_name)
@@ -461,8 +469,8 @@ class baseBufferController(baseBuffers.buffer):
screen_name = tweet.screen_name
users = [screen_name]
else:
screen_name = tweet.user.screen_name
users = utils.get_all_users(tweet, self.session.db)
screen_name = self.session.get_user(tweet.user).screen_name
users = utils.get_all_users(tweet, self.session)
dm = messages.dm(self.session, _(u"Direct message to %s") % (screen_name,), _(u"New direct message"), users)
if dm.message.get_response() == widgetUtils.OK:
screen_name = dm.message.get("cb")
@@ -471,10 +479,12 @@ class baseBufferController(baseBuffers.buffer):
text = dm.message.get_text()
val = self.session.api_call(call_name="send_direct_message", recipient_id=recipient_id, text=text)
if val != None:
sent_dms = self.session.db["sent_direct_messages"]
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db["sent_direct_messages"].append(val)
sent_dms.append(val)
else:
self.session.db["sent_direct_messages"].insert(0, val)
sent_dms.insert(0, val)
self.session.db["sent_direct_messages"] = sent_dms
pub.sendMessage("sent-dm", data=val, user=self.session.db["user_name"])
if hasattr(dm.message, "destroy"): dm.message.destroy()
@@ -501,12 +511,12 @@ class baseBufferController(baseBuffers.buffer):
comments = tweet.full_text
else:
comments = tweet.text
retweet = messages.tweet(self.session, _(u"Quote"), _(u"Add your comment to the tweet"), u"“@%s: %s" % (tweet.user.screen_name, comments), max=256, messageType="retweet")
retweet = messages.tweet(self.session, _(u"Quote"), _(u"Add your comment to the tweet"), u"“@%s: %s" % (self.session.get_user(tweet.user).screen_name, comments), max=256, messageType="retweet")
if comment != '':
retweet.message.set_text(comment)
if retweet.message.get_response() == widgetUtils.OK:
text = retweet.message.get_text()
text = text+" https://twitter.com/{0}/status/{1}".format(tweet.user.screen_name, id)
text = text+" https://twitter.com/{0}/status/{1}".format(self.session.get_user(tweet.user).screen_name, id)
if retweet.image == None:
item = self.session.api_call(call_name="update_status", _sound="retweet_send.ogg", status=text, in_reply_to_status_id=id, tweet_mode="extended")
if item != None:
@@ -543,7 +553,7 @@ class baseBufferController(baseBuffers.buffer):
return sound.URLPlayer.stop_audio()
tweet = self.get_tweet()
if tweet == None: return
urls = utils.find_urls(tweet)
urls = utils.find_urls(tweet, twitter_media=True)
if len(urls) == 1:
url=urls[0]
elif len(urls) > 1:
@@ -588,16 +598,18 @@ class baseBufferController(baseBuffers.buffer):
if self.type == "events" or self.type == "people" or self.type == "empty" or self.type == "account": return
answer = commonMessageDialogs.delete_tweet_dialog(None)
if answer == widgetUtils.YES:
items = self.session.db[self.name]
try:
if self.name == "direct_messages" or self.name == "sent_direct_messages":
self.session.twitter.destroy_direct_message(id=self.get_right_tweet().id)
self.session.db[self.name].pop(index)
items.pop(index)
else:
self.session.twitter.destroy_status(id=self.get_right_tweet().id)
self.session.db[self.name].pop(index)
items.pop(index)
self.buffer.list.remove_item(index)
except TweepError:
self.session.sound.play("error.ogg")
self.session.db[self.name] = items
@_tweets_exist
def user_details(self):
@@ -607,7 +619,7 @@ class baseBufferController(baseBuffers.buffer):
elif self.type == "people":
users = [tweet.screen_name]
else:
users = utils.get_all_users(tweet, self.session.db)
users = utils.get_all_users(tweet, self.session)
dlg = dialogs.utils.selectUserDialog(title=_(u"User details"), users=users)
if dlg.get_response() == widgetUtils.OK:
user.profileController(session=self.session, user=dlg.get_user())
@@ -625,7 +637,7 @@ class baseBufferController(baseBuffers.buffer):
def open_in_browser(self, *args, **kwargs):
tweet = self.get_tweet()
output.speak(_(u"Opening item in web browser..."))
url = "https://twitter.com/{screen_name}/status/{tweet_id}".format(screen_name=tweet.user.screen_name, tweet_id=tweet.id)
url = "https://twitter.com/{screen_name}/status/{tweet_id}".format(screen_name=self.session.get_user(tweet.user).screen_name, tweet_id=tweet.id)
webbrowser.open(url)
class directMessagesController(baseBufferController):
@@ -646,7 +658,9 @@ class directMessagesController(baseBufferController):
items, cursor = items
if type(cursor) == tuple:
cursor = cursor[1]
self.session.db["cursors"][self.name] = cursor
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in items]
items = results
log.debug("Retrieved %d items for cursored search in function %s" % (len(items), self.function))
@@ -657,22 +671,26 @@ class directMessagesController(baseBufferController):
return
sent = []
received = []
sent_dms = self.session.db["sent_direct_messages"]
received_dms = self.session.db["direct_messages"]
for i in items:
if int(i.message_create["sender_id"]) == self.session.db["user_id"]:
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db["sent_direct_messages"].insert(0, i)
sent_dms.insert(0, i)
sent.append(i)
else:
self.session.db["sent_direct_messages"].append(i)
sent_dms.append(i)
sent.insert(0, i)
else:
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[self.name].insert(0, i)
received_dms.insert(0, i)
received.append(i)
else:
self.session.db[self.name].append(i)
received_dms.append(i)
received.insert(0, i)
total = total+1
self.session.db["direct_messages"] = received_dms
self.session.db["sent_direct_messages"] = sent_dms
user_ids = [item.message_create["sender_id"] for item in items]
self.session.save_users(user_ids)
pub.sendMessage("more-sent-dms", data=sent, account=self.session.db["user_name"])
@@ -885,7 +903,9 @@ class peopleBufferController(baseBufferController):
val, cursor = val
if type(cursor) == tuple:
cursor = cursor[1]
self.session.db["cursors"][self.name] = cursor
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in val]
val = results
val.reverse()
@@ -914,7 +934,9 @@ class peopleBufferController(baseBufferController):
items, cursor = items
if type(cursor) == tuple:
cursor = cursor[1]
self.session.db["cursors"][self.name] = cursor
cursors = self.session.db["cursors"]
cursors[self.name] = cursor
self.session.db["cursors"] = cursors
results = [i for i in items]
items = results
log.debug("Retrieved %d items from cursored search in function %s" % (len(items), self.function))
@@ -923,11 +945,13 @@ class peopleBufferController(baseBufferController):
return
if items == None:
return
items_db = self.session.db[self.name]
for i in items:
if self.session.settings["general"]["reverse_timelines"] == False:
self.session.db[self.name].insert(0, i)
items_db.insert(0, i)
else:
self.session.db[self.name].append(i)
items_db.append(i)
self.session.db[self.name] = items_db
selected = self.buffer.list.get_selected()
if self.session.settings["general"]["reverse_timelines"] == True:
for i in items:
@@ -1206,6 +1230,8 @@ class conversationBufferController(searchBufferController):
self.statuses.append(self.tweet)
self.ids.append(self.tweet.id)
tweet = self.tweet
if not hasattr(tweet, "in_reply_to_status_id"):
tweet.in_reply_to_status_id = None
while tweet.in_reply_to_status_id != None:
try:
tweet = self.session.twitter.get_status(id=tweet.in_reply_to_status_id, tweet_mode="extended")

View File

@@ -254,8 +254,8 @@ class Controller(object):
# Connection checker executed each minute.
self.checker_function = RepeatingTimer(60, self.check_connection)
# self.checker_function.start()
self.save_db = RepeatingTimer(300, self.save_data_in_db)
self.save_db.start()
# self.save_db = RepeatingTimer(300, self.save_data_in_db)
# self.save_db.start()
log.debug("Setting updates to buffers every %d seconds..." % (60*config.app["app-settings"]["update_period"],))
self.update_buffers_function = RepeatingTimer(60*config.app["app-settings"]["update_period"], self.update_buffers)
self.update_buffers_function.start()
@@ -530,7 +530,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user()
@@ -547,7 +547,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user()
@@ -575,7 +575,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.utils.selectUserDialog(_(u"Select the user"), users)
if dlg.get_response() == widgetUtils.OK:
user = dlg.get_user()
@@ -617,6 +617,7 @@ class Controller(object):
if d.needs_restart == True:
commonMessageDialogs.needs_restart()
buff.session.settings.write()
buff.session.save_persistent_data()
restart.restart_program()
def report_error(self, *args, **kwargs):
@@ -651,8 +652,8 @@ class Controller(object):
if sessions.sessions[item].logged == False: continue
log.debug("Disconnecting streams for %s session" % (sessions.sessions[item].session_id,))
sessions.sessions[item].sound.cleaner.cancel()
log.debug("Shelving database for " + sessions.sessions[item].session_id)
sessions.sessions[item].shelve()
log.debug("Saving database for " + sessions.sessions[item].session_id)
sessions.sessions[item].save_persistent_data()
if system == "Windows":
self.systrayIcon.RemoveIcon()
pidpath = os.path.join(os.getenv("temp"), "{}.pid".format(application.name))
@@ -669,7 +670,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users)
def unfollow(self, *args, **kwargs):
@@ -681,7 +682,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "unfollow")
def mute(self, *args, **kwargs):
@@ -693,7 +694,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "mute")
def unmute(self, *args, **kwargs):
@@ -705,7 +706,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "unmute")
def block(self, *args, **kwargs):
@@ -717,7 +718,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "block")
def unblock(self, *args, **kwargs):
@@ -729,7 +730,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "unblock")
def report(self, *args, **kwargs):
@@ -741,7 +742,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
u = userActionsController.userActionsController(buff, users, "report")
def post_tweet(self, event=None):
@@ -828,7 +829,7 @@ class Controller(object):
elif buff.type == "dm":
users = [buff.session.get_user(tweet.message_create["sender_id"]).screen_name]
else:
users = utils.get_all_users(tweet, buff.session.db)
users = utils.get_all_users(tweet, buff.session)
dlg = dialogs.userSelection.selectUserDialog(users=users, default=default)
if dlg.get_response() == widgetUtils.OK:
usr = utils.if_user_exists(buff.session.twitter, dlg.get_user())
@@ -923,8 +924,8 @@ class Controller(object):
def open_conversation(self, *args, **kwargs):
buffer = self.get_current_buffer()
id = buffer.get_right_tweet().id_str
user = buffer.get_right_tweet().user.screen_name
id = buffer.get_right_tweet().id
user = buffer.session.get_user(buffer.get_right_tweet().user).screen_name
search = twitterBuffers.conversationBufferController(self.view.nb, "search", "%s-searchterm" % (id,), buffer.session, buffer.session.db["user_name"], bufferType="searchPanel", sound="search_updated.ogg", since_id=id, q="@{0}".format(user,))
search.tweet = buffer.get_right_tweet()
search.start_stream(start=True)
@@ -1274,10 +1275,12 @@ class Controller(object):
data = buffer.session.check_long_tweet(data)
if data == False: # Long tweet deleted from twishort.
return
items = buffer.session.db[buffer.name]
if buffer.session.settings["general"]["reverse_timelines"] == False:
buffer.session.db[buffer.name].append(data)
items.append(data)
else:
buffer.session.db[buffer.name].insert(0, data)
items.insert(0, data)
buffer.session.db[buffer.name] = items
buffer.add_new_item(data)
def manage_friend(self, data, user):
@@ -1623,4 +1626,4 @@ class Controller(object):
def save_data_in_db(self):
for i in sessions.sessions:
sessions.sessions[i].shelve()
sessions.sessions[i].save_persistent_data()

View File

@@ -109,7 +109,7 @@ class basicTweet(object):
if results.weightedLength > self.max:
self.session.sound.play("max_length.ogg")
else:
self.message.set_title(_(u"%s - %s characters") % (self.title, results.weightedLength))
self.message.set_title(_(u"%s - %s characters") % (self.title, len(self.message.get_text())))
def spellcheck(self, event=None):
text = self.message.get_text()

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from builtins import str
from builtins import object
import os
import webbrowser
import sound_lib
@@ -151,6 +148,7 @@ class accountSettingsController(globalSettingsController):
else:
self.dialog.set_value("general", "retweet_mode", _(u"Retweet with comments"))
self.dialog.set_value("general", "persist_size", str(self.config["general"]["persist_size"]))
self.dialog.set_value("general", "load_cache_in_memory", self.config["general"]["load_cache_in_memory"])
self.dialog.create_reporting()
self.dialog.set_value("reporting", "speech_reporting", self.config["reporting"]["speech_reporting"])
self.dialog.set_value("reporting", "braille_reporting", self.config["reporting"]["braille_reporting"])
@@ -193,6 +191,9 @@ class accountSettingsController(globalSettingsController):
self.config["general"]["relative_times"] = self.dialog.get_value("general", "relative_time")
self.config["general"]["show_screen_names"] = self.dialog.get_value("general", "show_screen_names")
self.config["general"]["max_tweets_per_call"] = self.dialog.get_value("general", "itemsPerApiCall")
if self.config["general"]["load_cache_in_memory"] != self.dialog.get_value("general", "load_cache_in_memory"):
self.config["general"]["load_cache_in_memory"] = self.dialog.get_value("general", "load_cache_in_memory")
self.needs_restart = True
if self.config["general"]["persist_size"] != self.dialog.get_value("general", "persist_size"):
if self.dialog.get_value("general", "persist_size") == '':
self.config["general"]["persist_size"] =-1

View File

@@ -1,5 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from arrow import locales
from arrow.locales import Locale
@@ -8,88 +7,43 @@ def fix():
locales.get_locale = get_locale
def get_locale(name):
locale_cls = locales._locales.get(name.lower())
locale_cls = locales._locale_map.get(name.lower())
if locale_cls is None:
name = name[:2]
locale_cls = locales._locales.get(name.lower())
locale_cls = locales._locale_map.get(name.lower())
if locale_cls == None:
return locales.EnglishLocale()
return locale_cls()
class CatalaLocale(Locale):
names = ['ca', 'ca_es', 'ca_ca']
past = 'Fa {0}'
future = '{0}' # I don't know what's the right phrase in catala for the future.
timeframes = {
'now': 'Ara mateix',
'seconds': 'segons',
'minute': '1 minut',
'minutes': '{0} minuts',
'hour': 'una hora',
'hours': '{0} hores',
'day': 'un dia',
'days': '{0} dies',
'month': 'un mes',
'months': '{0} messos',
'year': 'un any',
'years': '{0} anys',
}
month_names = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre']
month_abbreviations = ['', 'Jener', 'Febrer', 'Març', 'Abril', 'Maig', 'Juny', 'Juliol', 'Agost', 'Setembre', 'Octubre', 'Novembre', 'Decembre']
day_names = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge']
day_abbreviations = ['', 'Dilluns', 'Dimars', 'Dimecres', 'Dijous', 'Divendres', 'Disabte', 'Diumenge']
class GalicianLocale(Locale):
class GalicianLocale(object):
names = ['gl', 'gl_es', 'gl_gl']
past = 'Fai {0}'
past = 'Hai {0}'
future = 'En {0}'
and_word = "e"
timeframes = {
'now': 'Agora mesmo',
'seconds': 'segundos',
'now': 'Agora',
"second": "un segundo",
'seconds': '{0} segundos',
'minute': 'un minuto',
'minutes': '{0} minutos',
'hour': 'una hora',
'hour': 'unha hora',
'hours': '{0} horas',
'day': 'un día',
'days': '{0} días',
"week": "unha semana",
"weeks": "{0} semanas",
'month': 'un mes',
'months': '{0} meses',
'year': 'un ano',
'years': '{0} anos',
}
month_names = ['', 'Xaneiro', 'Febreiro', 'Marzo', 'Abril', 'Maio', 'Xuño', 'Xullo', 'Agosto', 'Setembro', 'Outubro', 'Novembro', 'Decembro']
month_abbreviations = ['', 'Xan', 'Feb', 'Mar', 'Abr', 'Mai', 'Xun', 'Xul', 'Ago', 'Set', 'Out', 'Nov', 'Dec']
day_names = ['', 'Luns', 'Martes', 'Mércores', 'Xoves', 'Venres', 'Sábado', 'Domingo']
day_abbreviations = ['', 'Lun', 'Mar', 'Mer', 'xov', 'Ven' 'Sab', 'Dom']
meridians = {"am": "am", "pm": "pm", "AM": "AM", "PM": "PM"}
class BasqueLocale(Locale):
names = ['eu', 'eu_es', 'eu_eu']
past = 'duela {0}'
future = '{0} igarota'
timeframes = {
'now': 'Orain',
# 'second': 'segundu bat',
'seconds': 'segundu batzuk', # without specifying a number.
#'seconds': '{0} segundu', # specifying a number
'minute': 'minutu bat',
'minutes': '{0} minutu',
'hour': 'ordu bat',
'hours': '{0} ordu',
'day': 'egun bat',
'days': '{0} egun',
'month': 'hilabete bat',
'months': '{0} hilabete',
'year': 'urte bat',
'years': '{0} urte',
}
month_names = ['', 'Urtarrilak', 'Otsailak', 'Martxoak', 'Apirilak', 'Maiatzak', 'Ekainak', 'Uztailak', 'Abuztuak', 'Irailak', 'Urriak', 'Azaroak', 'Abenduak']
month_abbreviations = ['', 'urt', 'ots', 'mar', 'api', 'mai', 'eka', 'uzt', 'abu', 'ira', 'urr', 'aza', 'abe']
day_names = ['', 'Asteleehna', 'Asteartea', 'Asteazkena', 'Osteguna', 'Ostirala', 'Larunbata', 'Igandea']
day_abbreviations = ['', 'al', 'ar', 'az', 'og', 'ol', 'lr', 'ig']
month_names = ['', 'xaneiro', 'febreiro', 'marzo', 'abril', 'maio', 'xuño', 'xullo', 'agosto', 'setembro', 'outubro', 'novembro', 'decembro']
month_abbreviations = ['', 'xan', 'feb', 'mar', 'abr', 'mai', 'xun', 'xul', 'ago', 'set', 'out', 'nov', 'dec']
day_names = ['', 'luns', 'martes', 'mércores', 'xoves', 'venres', 'sábado', 'domingo']
day_abbreviations = ['', 'lun', 'mar', 'mer', 'xov', 'ven', 'sab', 'dom']
ordinal_day_re = r"((?P<value>[1-3]?[0-9](?=[ºª]))[ºª])"

Binary file not shown.

View File

@@ -2,14 +2,14 @@ msgid ""
msgstr ""
"Project-Id-Version: TW Blue 0.94\n"
"POT-Creation-Date: 2019-03-17 13:34+Hora estándar romance\n"
"PO-Revision-Date: 2019-07-22 16:16+0200\n"
"PO-Revision-Date: 2020-06-04 21:19+0200\n"
"Last-Translator: Corentin BACQUÉ-CAZENAVE <corentin@progaccess33.net>\n"
"Language-Team: Corentin BACQUÉ-CAZENAVE <corentin@progaccess.net>\n"
"Language-Team: Oreonan <corentin@progaccess.net>\n"
"Language: fr\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Poedit 2.2.3\n"
"X-Generator: Poedit 2.3.1\n"
"X-Poedit-KeywordsList: _;gettext;gettext_noop\n"
"X-Poedit-Basepath: .\n"
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
@@ -262,7 +262,7 @@ msgstr "Listes"
#: ../src\controller\mainController.py:378
#: ../src\controller\mainController.py:1399
msgid "List for {}"
msgstr "Liste de {}"
msgstr "Liste {}"
#: ../src\controller\mainController.py:381
msgid "Searches"
@@ -1988,7 +1988,7 @@ msgstr "Téléchargement de la nouvelle version en cours..."
#: ../src\update\wxUpdater.py:28
msgid "Updating... %s of %s"
msgstr "Mise à jour en cours... %s de %s"
msgstr "Mise à jour en cours... %s sur %s"
#: ../src\update\wxUpdater.py:31
msgid "Done!"

File diff suppressed because it is too large Load Diff

Binary file not shown.

View File

@@ -5,16 +5,16 @@
msgid ""
msgstr ""
"Project-Id-Version: TwBlue 0.80\n"
"POT-Creation-Date: 2019-03-17 13:34+Hora estndar romance\n"
"PO-Revision-Date: 2018-08-07 13:46-0500\n"
"Last-Translator: Manuel Cortez <manuel@manuelcortez.net>\n"
"POT-Creation-Date: 2019-03-17 13:34+Hora estándar romance\n"
"PO-Revision-Date: 2021-06-25 18:32+0100\n"
"Last-Translator: Nikola Jović <wwenikola123@gmail.com>\n"
"Language-Team: Aleksandar Đurić <agasoft@gmail.com>\n"
"Language: sr_RS@latin\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Generated-By: pygettext.py 1.5\n"
"X-Generator: Poedit 2.0.1\n"
"X-Generator: Poedit 1.6.10\n"
"X-Poedit-Bookmarks: -1,442,-1,-1,-1,-1,-1,-1,-1,-1\n"
"X-Poedit-SourceCharset: UTF-8\n"
@@ -82,26 +82,24 @@ msgid "Muted users"
msgstr "Utišani korisnici"
#: ../src\controller\buffers\twitterBuffers.py:75
#, fuzzy
msgid "{username}'s timeline"
msgstr "Otvori korisničku vremensku crtu"
msgstr "Vremenska linija korisnika {username}"
#: ../src\controller\buffers\twitterBuffers.py:77
msgid "{username}'s likes"
msgstr ""
msgstr "Sviđanja korisnika {username}"
#: ../src\controller\buffers\twitterBuffers.py:79
msgid "{username}'s followers"
msgstr ""
msgstr "Pratioci korisnika {username}"
#: ../src\controller\buffers\twitterBuffers.py:81
msgid "{username}'s friends"
msgstr ""
msgstr "Prijatelji korisnika {username}"
#: ../src\controller\buffers\twitterBuffers.py:83
#, fuzzy
msgid "Unknown buffer"
msgstr "Nepoznato"
msgstr "Nepoznat kanal"
#: ../src\controller\buffers\twitterBuffers.py:86
#: ../src\controller\buffers\twitterBuffers.py:1242
@@ -117,14 +115,12 @@ msgid "Write the tweet here"
msgstr "Otkucajte tvit ovde:"
#: ../src\controller\buffers\twitterBuffers.py:194
#, fuzzy
msgid "New tweet in {0}"
msgstr "Novi tvit"
msgstr "Novi tvit u kanalu {0}"
#: ../src\controller\buffers\twitterBuffers.py:197
#, fuzzy
msgid "{0} new tweets in {1}."
msgstr "@{0} citira vaš tvit: {1}"
msgstr "{0} novih tvitova u kanalu {1}."
#: ../src\controller\buffers\twitterBuffers.py:232
#: ../src\controller\buffers\twitterBuffers.py:676
@@ -180,7 +176,7 @@ msgstr "Podaci o korisniku"
#: ../src\controller\buffers\twitterBuffers.py:634
#: ../src\controller\buffers\twitterBuffers.py:987
msgid "Opening item in web browser..."
msgstr ""
msgstr "Otvaram stavku u Web pretraživaču..."
#: ../src\controller\buffers\twitterBuffers.py:688
#: ../src\controller\buffers\twitterBuffers.py:855
@@ -194,30 +190,28 @@ msgid "Mention"
msgstr "Spomeni"
#: ../src\controller\buffers\twitterBuffers.py:728
#, fuzzy
msgid "{0} new direct messages."
msgstr "Nova direktna poruka"
msgstr "{0} novih direktnih poruka."
#: ../src\controller\buffers\twitterBuffers.py:731
#, fuzzy
msgid "This action is not supported in the buffer yet."
msgstr "Ova radnja nije podržana na ovom kanalu"
msgstr "Ova radnja još uvek nije podržana na ovom kanalu."
#: ../src\controller\buffers\twitterBuffers.py:741
msgid ""
"Getting more items cannot be done in this buffer. Use the direct messages "
"buffer instead."
msgstr ""
"Nemoguće preuzeti dodatne stavke za ovaj kanal. Umesto toga, koristite kanal "
"direktne poruke."
#: ../src\controller\buffers\twitterBuffers.py:983
#, fuzzy
msgid "{0} new followers."
msgstr "Novi pratilac."
msgstr "{0} novih pratilaca."
#: ../src\controller\buffers\twitterBuffers.py:1266
#, fuzzy
msgid "This action is not supported in the buffer, yet."
msgstr "Ova radnja nije podržana na ovom kanalu"
msgstr "Ova radnja još uvek nije podržana na ovom kanalu"
#: ../src\controller\mainController.py:273
msgid "Ready"
@@ -308,9 +302,8 @@ msgid "{0} not found."
msgstr "{0} nije pronađen."
#: ../src\controller\mainController.py:482
#, fuzzy
msgid "Filters cannot be applied on this buffer"
msgstr "Ova radnja nije podržana na ovom kanalu"
msgstr "Filteri se ne mogu primeniti na ovaj kanal"
#: ../src\controller\mainController.py:535
#: ../src\controller\mainController.py:552
@@ -319,9 +312,8 @@ msgid "Select the user"
msgstr "Izaberite korisnika"
#: ../src\controller\mainController.py:809 ../src\controller\messages.py:236
#, fuzzy
msgid "MMM D, YYYY. H:m"
msgstr "dddd, MMMM D, YYYY H:m:s"
msgstr "MMM D, YYYY. H:m"
#: ../src\controller\mainController.py:934
msgid "Conversation with {0}"
@@ -379,12 +371,11 @@ msgstr "Ova lista je već otvorena"
#: ../src\controller\mainController.py:1423
#: ../src\controller\mainController.py:1439
#, fuzzy
msgid ""
"An error happened while trying to connect to the server. Please try later."
msgstr ""
"Dogodilo se nešto neočekivano prilikom prijavljivanja greške. Molimo vas da "
"pokušate kasnije."
"Došlo je do greške pri pokušaju povezivanja na server. Molimo pokušajte "
"kasnije."
#: ../src\controller\mainController.py:1475
msgid "The auto-reading of new tweets is enabled for this buffer"
@@ -475,9 +466,8 @@ msgid "%s - %s characters"
msgstr "%s - %s znakova"
#: ../src\controller\messages.py:262
#, fuzzy
msgid "View item"
msgstr "Vidi liste"
msgstr "Prikaži stavku"
#: ../src\controller\settings.py:75
msgid "Direct connection"
@@ -533,7 +523,8 @@ msgstr "Korisnik je suspendovan."
msgid "Information for %s"
msgstr "Informacija za %s"
#: ../src\controller\user.py:66 ../src\extra\AudioUploader\audioUploader.py:124
#: ../src\controller\user.py:66
#: ../src\extra\AudioUploader\audioUploader.py:124
msgid "Discarded"
msgstr "Odbačeno"
@@ -658,9 +649,8 @@ msgstr "Snimam zvučni zapis..."
#: ../src\extra\AudioUploader\transfer.py:78
#: ../src\extra\AudioUploader\transfer.py:84
#, fuzzy
msgid "Error in file upload: {0}"
msgstr "Kod greške {0}"
msgstr "Greška u otpremanju datoteke: {0}"
#: ../src\extra\AudioUploader\utils.py:27 ../src\update\utils.py:27
msgid "%d day, "
@@ -871,28 +861,24 @@ msgid "Suggestions"
msgstr "Predlozi"
#: ../src\extra\SpellChecker\wx_ui.py:42
#, fuzzy
msgid "&Ignore"
msgstr "Zamenari."
msgstr "&Zanemari"
#: ../src\extra\SpellChecker\wx_ui.py:43
#, fuzzy
msgid "I&gnore all"
msgstr "Zanemari sve"
msgstr "Z&anemai sve"
#: ../src\extra\SpellChecker\wx_ui.py:44
#, fuzzy
msgid "&Replace"
msgstr "Zameni"
msgstr "&Zameni"
#: ../src\extra\SpellChecker\wx_ui.py:45
#, fuzzy
msgid "R&eplace all"
msgstr "Zameni sve"
msgstr "Zam&eni sve"
#: ../src\extra\SpellChecker\wx_ui.py:46
msgid "&Add to personal dictionary"
msgstr ""
msgstr "&Dodaj u lični rečnik"
#: ../src\extra\SpellChecker\wx_ui.py:79
msgid ""
@@ -1553,9 +1539,8 @@ msgid "Like a tweet"
msgstr "Označi tvit sa sviđa mi se"
#: ../src\keystrokeEditor\constants.py:15
#, fuzzy
msgid "Like/unlike a tweet"
msgstr "Označi tvit sa ne sviđa mi se"
msgstr "Označi da ti se tvit sviđa / ne sviđa"
#: ../src\keystrokeEditor\constants.py:16
msgid "Unlike a tweet"
@@ -1594,9 +1579,8 @@ msgid "Open URL"
msgstr "Otvori vezu"
#: ../src\keystrokeEditor\constants.py:25
#, fuzzy
msgid "View in Twitter"
msgstr "Pretraži ttwitter"
msgstr "Prikaži na Twitteru"
#: ../src\keystrokeEditor\constants.py:26
msgid "Increase volume by 5%"
@@ -1715,9 +1699,8 @@ msgid "Opens the global settings dialogue"
msgstr "Otvara dijalog globalnih podešavanja"
#: ../src\keystrokeEditor\constants.py:54
#, fuzzy
msgid "Opens the list manager"
msgstr "Upravljanje listama"
msgstr "Otvara upravljanje listama"
#: ../src\keystrokeEditor\constants.py:55
msgid "Opens the account settings dialogue"
@@ -1821,6 +1804,10 @@ msgid ""
"If you're sure that {0} isn't running, try deleting the file at {1}. If "
"you're unsure of how to do this, contact the {0} developers."
msgstr ""
"{0} je već pokrenut. Zatvorite druge pokrenute prozore aplikacije pre "
"pokretanja ovog. Ako ste sigurni da {0} nije pokrenut, pokušajte da obrišete "
"datoteku koja se nalazi na {1}. Ako niste sigurni kako ovo da uradite, "
"kontaktirajte {0} programere."
#: ../src\sessionmanager\wxUI.py:8
msgid "Session manager"
@@ -1926,9 +1913,8 @@ msgid "public"
msgstr "Javno"
#: ../src\sessions\twitter\session.py:169
#, fuzzy
msgid "There are no more items to retrieve in this buffer."
msgstr "Nema koordinata u ovom tvitu."
msgstr "Nema više stavki koje se mogu preuzeti u ovom kanalu."
#: ../src\sessions\twitter\session.py:215
msgid "%s failed. Reason: %s"
@@ -1951,13 +1937,12 @@ msgid "Error code {0}"
msgstr "Kod greške {0}"
#: ../src\sessions\twitter\wxUI.py:6
#, fuzzy
msgid "Authorising account..."
msgstr "Autorizovan nalog %d"
msgstr "Autorizacija naloga..."
#: ../src\sessions\twitter\wxUI.py:9
msgid "Enter your PIN code here"
msgstr ""
msgstr "Ovde upišite vaš PIN kod"
#: ../src\sound.py:159
msgid "Stopped."
@@ -2266,19 +2251,20 @@ msgstr ""
"suspendovan sa tvitera."
#: ../src\wxUI\commonMessageDialogs.py:85
#, fuzzy
msgid "Do you really want to delete this filter?"
msgstr "Želite li zaista da izbrišete ovu listu?"
msgstr "Da li zaista želite da izbrišete ovaj filter?"
#: ../src\wxUI\commonMessageDialogs.py:88
msgid "This filter already exists. Please use a different title"
msgstr ""
msgstr "Ovaj filter već postoji. Molimo koristite drugi naziv"
#: ../src\wxUI\commonMessageDialogs.py:94
msgid ""
"{0} quit unexpectedly the last time it was run. If the problem persists, "
"please report it to the {0} developers."
msgstr ""
"{0} je neočekivano zatvoren pri poslednjem pokretanju. Ako se problem "
"nastavi, molimo prijavite ga{0} programerima."
#: ../src\wxUI\dialogs\attach.py:9
msgid "Add an attachment"
@@ -2343,11 +2329,11 @@ msgstr "Pitaj pre zatvaranja {0}"
#: ../src\wxUI\dialogs\configuration.py:27
msgid "Disable Streaming functions"
msgstr ""
msgstr "Onemogući streaming funkcije"
#: ../src\wxUI\dialogs\configuration.py:30
msgid "Buffer update interval, in minutes"
msgstr ""
msgstr "Interval ažuriranja kanala, u minutima"
#: ../src\wxUI\dialogs\configuration.py:36
msgid "Play a sound when {0} launches"
@@ -2378,7 +2364,7 @@ msgstr ""
#: ../src\wxUI\dialogs\configuration.py:48
msgid "Remember state for mention all and long tweet"
msgstr ""
msgstr "Pamti stanje opcija spomeni sve i dug tvit"
#: ../src\wxUI\dialogs\configuration.py:51
msgid "Keymap"
@@ -2446,11 +2432,11 @@ msgstr ""
#: ../src\wxUI\dialogs\configuration.py:134
msgid "Enable automatic speech feedback"
msgstr ""
msgstr "Omogući automatske govorne povratne informacije"
#: ../src\wxUI\dialogs\configuration.py:136
msgid "Enable automatic Braille feedback"
msgstr ""
msgstr "Omogući automatske povratne informacije na brajevom redu"
#: ../src\wxUI\dialogs\configuration.py:144
msgid "Status"
@@ -2564,7 +2550,7 @@ msgstr "Proksi"
#: ../src\wxUI\dialogs\configuration.py:373
msgid "Feedback"
msgstr ""
msgstr "Povratne informacije"
#: ../src\wxUI\dialogs\configuration.py:377
msgid "Buffers"
@@ -2583,81 +2569,74 @@ msgid "Save"
msgstr "Sačuvaj"
#: ../src\wxUI\dialogs\filterDialogs.py:15
#, fuzzy
msgid "Create a filter for this buffer"
msgstr "Stvori kanal trenutnih tema u trendu"
msgstr "Napravi filter za ovaj kanal"
#: ../src\wxUI\dialogs\filterDialogs.py:16
msgid "Filter title"
msgstr ""
msgstr "Naziv filtera"
#: ../src\wxUI\dialogs\filterDialogs.py:25
#: ../src\wxUI\dialogs\filterDialogs.py:125
msgid "Filter by word"
msgstr ""
msgstr "Filtriraj na osnovu reči"
#: ../src\wxUI\dialogs\filterDialogs.py:26
msgid "Ignore tweets wich contain the following word"
msgstr ""
msgstr "Zanemari tvitove koji sadrže sledeću reč"
#: ../src\wxUI\dialogs\filterDialogs.py:27
#, fuzzy
msgid "Ignore tweets without the following word"
msgstr "Zanemari tvitove iz ovog klijenta"
msgstr "Zanemari tvitove bez sledeće reči"
#: ../src\wxUI\dialogs\filterDialogs.py:32
msgid "word"
msgstr ""
msgstr "Reč"
#: ../src\wxUI\dialogs\filterDialogs.py:37
#, fuzzy
msgid "Allow retweets"
msgstr "Prikaži tvit"
msgstr "Dozvoli retvitove"
#: ../src\wxUI\dialogs\filterDialogs.py:38
msgid "Allow quoted tweets"
msgstr ""
msgstr "Dozvoli citirane tvitove"
#: ../src\wxUI\dialogs\filterDialogs.py:39
#, fuzzy
msgid "Allow replies"
msgstr "Vremenska linija pratilaca"
msgstr "Dozvoli odgovore"
#: ../src\wxUI\dialogs\filterDialogs.py:47
msgid "Use this term as a regular expression"
msgstr ""
msgstr "Koristi ovaj termin kao regulara nizraz"
#: ../src\wxUI\dialogs\filterDialogs.py:49
#: ../src\wxUI\dialogs\filterDialogs.py:125
#, fuzzy
msgid "Filter by language"
msgstr "Izvorni jezik"
msgstr "Filtriraj na osnovu jezika"
#: ../src\wxUI\dialogs\filterDialogs.py:50
msgid "Load tweets in the following languages"
msgstr ""
msgstr "Učitaj tvitove na sledećim jezicima"
#: ../src\wxUI\dialogs\filterDialogs.py:51
msgid "Ignore tweets in the following languages"
msgstr ""
msgstr "Zanemari tvitove na sledećim jezicima"
#: ../src\wxUI\dialogs\filterDialogs.py:52
msgid "Don't filter by language"
msgstr ""
msgstr "Ne filtriraj na osnovu jezika"
#: ../src\wxUI\dialogs\filterDialogs.py:63
#, fuzzy
msgid "Supported languages"
msgstr "Izvorni jezik"
msgstr "Podržani jezici"
#: ../src\wxUI\dialogs\filterDialogs.py:68
msgid "Add selected language to filter"
msgstr ""
msgstr "Dodaj izabrani jezik u filter"
#: ../src\wxUI\dialogs\filterDialogs.py:72
#, fuzzy
msgid "Selected languages"
msgstr "Izvorni jezik"
msgstr "Izabrani jezici"
#: ../src\wxUI\dialogs\filterDialogs.py:74
#: ../src\wxUI\dialogs\filterDialogs.py:132 ../src\wxUI\dialogs\lists.py:20
@@ -2666,17 +2645,16 @@ msgid "Remove"
msgstr "Ukloni"
#: ../src\wxUI\dialogs\filterDialogs.py:122
#, fuzzy
msgid "Manage filters"
msgstr "Upravljaj nalozima"
msgstr "Upravljanje filterima"
#: ../src\wxUI\dialogs\filterDialogs.py:124
msgid "Filters"
msgstr ""
msgstr "Filteri"
#: ../src\wxUI\dialogs\filterDialogs.py:125
msgid "Filter"
msgstr ""
msgstr "Filter"
#: ../src\wxUI\dialogs\find.py:12
msgid "Find in current buffer"
@@ -2844,9 +2822,8 @@ msgid "Source: "
msgstr "Izvor:"
#: ../src\wxUI\dialogs\message.py:342 ../src\wxUI\dialogs\message.py:420
#, fuzzy
msgid "Date: "
msgstr "Datum"
msgstr "Datum:"
#: ../src\wxUI\dialogs\message.py:405
msgid "View"
@@ -2942,9 +2919,8 @@ msgid "Update your profile"
msgstr "Ažurirajte vaš profil"
#: ../src\wxUI\dialogs\update_profile.py:11
#, fuzzy
msgid "&Name (50 characters maximum)"
msgstr "Ime, najviše 20 znakova"
msgstr "&Ime(najviše 50 znakova)"
#: ../src\wxUI\dialogs\update_profile.py:22
msgid "&Website"
@@ -3060,9 +3036,8 @@ msgid "&Open URL"
msgstr "otvori vezu"
#: ../src\wxUI\menus.py:17 ../src\wxUI\menus.py:53 ../src\wxUI\menus.py:86
#, fuzzy
msgid "&Open in Twitter"
msgstr "Pretraži ttwitter"
msgstr "&Otvori na Twitteru"
#: ../src\wxUI\menus.py:19 ../src\wxUI\menus.py:37 ../src\wxUI\menus.py:55
msgid "&Play audio"
@@ -3215,14 +3190,12 @@ msgid "New &trending topics buffer..."
msgstr "Novi kanal sa temama u trendu"
#: ../src\wxUI\view.py:54
#, fuzzy
msgid "Create a &filter"
msgstr "Stvori novu listu"
msgstr "Napravi &filter"
#: ../src\wxUI\view.py:55
#, fuzzy
msgid "&Manage filters"
msgstr "Upravljaj nalozima"
msgstr "&Upravljanje filterima"
#: ../src\wxUI\view.py:56
msgid "Find a string in the currently focused buffer..."
@@ -3274,7 +3247,7 @@ msgstr "{0} &website"
#: ../src\wxUI\view.py:77
msgid "Get soundpacks for TWBlue"
msgstr ""
msgstr "Nabavi zvučne pakete za TW Blue"
#: ../src\wxUI\view.py:78
msgid "About &{0}"

18
src/run_tests.py Normal file
View File

@@ -0,0 +1,18 @@
# -*- coding: utf-8 -*-
import unittest
testmodules = ["test.test_cache"]
suite = unittest.TestSuite()
for t in testmodules:
try:
# If the module defines a suite() function, call it to get the suite.
mod = __import__(t, globals(), locals(), ['suite'])
suitefn = getattr(mod, 'suite')
suite.addTest(suitefn())
except (ImportError, AttributeError):
# else, just load all the test cases from the module.
suite.addTest(unittest.defaultTestLoader.loadTestsFromName(t))
unittest.TextTestRunner(verbosity=2).run(suite)

View File

@@ -1,9 +1,5 @@
# -*- coding: utf-8 -*-
""" A base class to be derived in possible new sessions for TWBlue and services."""
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import str
from builtins import object
import os
import paths
import output
@@ -11,9 +7,8 @@ import time
import sound
import logging
import config_utils
import shelve
import sqlitedict
import application
import os
from . import session_exceptions as Exceptions
log = logging.getLogger("sessionmanager.session")
@@ -59,7 +54,7 @@ class baseSession(object):
log.debug("Creating config file %s" % (file_,))
self.settings = config_utils.load_config(os.path.join(paths.config_path(), file_), os.path.join(paths.app_path(), "Conf.defaults"))
self.init_sound()
self.deshelve()
self.load_persistent_data()
def init_sound(self):
try: self.sound = sound.soundSystem(self.settings["sound"])
@@ -73,48 +68,88 @@ class baseSession(object):
def authorise(self):
pass
def shelve(self):
"""Shelve the database to allow for persistance."""
shelfname=os.path.join(paths.config_path(), str(self.session_id), "cache")
if self.settings["general"]["persist_size"] == 0:
if os.path.exists(shelfname+".dat"):
os.remove(shelfname+".dat")
return
try:
if not os.path.exists(shelfname+".dat"):
output.speak("Generating database, this might take a while.",True)
shelf=shelve.open(os.path.join(paths.config_path(), shelfname),'c')
for key, value in list(self.db.items()):
if type(key) != str and type(key) != str:
output.speak("Uh oh, while shelving the database, a key of type " + str(type(key)) + " has been found. It will be converted to type str, but this will cause all sorts of problems on deshelve. Please bring this to the attention of the " + application.name + " developers immediately. More information about the error will be written to the error log.",True)
log.error("Uh oh, " + str(key) + " is of type " + str(type(key)) + "!")
if type(value) == list and self.settings["general"]["persist_size"] != -1 and len(value) > self.settings["general"]["persist_size"]:
shelf[key]=value[self.settings["general"]["persist_size"]:]
def get_sized_buffer(self, buffer, size, reversed=False):
""" Returns a list with the amount of items specified by size."""
if isinstance(buffer, list) and size != -1 and len(buffer) > size:
log.debug("Requesting {} items from a list of {} items. Reversed mode: {}".format(size, len(buffer), reversed))
if reversed == True:
return buffer[:size]
else:
shelf[key]=value
shelf.close()
except:
output.speak("An exception occurred while shelving the " + application.name + " database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the " + application.name + " developers.",True)
log.exception("Exception while shelving" + shelfname)
os.remove(shelfname)
return buffer[len(buffer)-size:]
else:
return buffer
def deshelve(self):
"""Import a shelved database."""
shelfname=os.path.join(paths.config_path(), str(self.session_id)+"/cache")
def save_persistent_data(self):
""" Save the data to a persistent sqlite backed file. ."""
dbname=os.path.join(paths.config_path(), str(self.session_id), "cache.db")
log.debug("Saving storage information...")
# persist_size set to 0 means not saving data actually.
if self.settings["general"]["persist_size"] == 0:
if os.path.exists(shelfname+".dat"):
os.remove(shelfname+".dat")
if os.path.exists(dbname):
os.remove(dbname)
return
# Let's check if we need to create a new SqliteDict object (when loading db in memory) or we just need to call to commit in self (if reading from disk).db.
# If we read from disk, we cannot modify the buffer size here as we could damage the app's integrity.
# We will modify buffer's size (managed by persist_size) upon loading the db into memory in app startup.
if self.settings["general"]["load_cache_in_memory"] and isinstance(self.db, dict):
log.debug("Opening database to dump memory contents...")
db=sqlitedict.SqliteDict(dbname, 'c')
for k in self.db.keys():
sized_buff = self.get_sized_buffer(self.db[k], self.settings["general"]["persist_size"], self.settings["general"]["reverse_timelines"])
db[k] = sized_buff
db.commit(blocking=True)
db.close()
log.debug("Data has been saved in the database.")
else:
try:
shelf=shelve.open(os.path.join(paths.config_path(), shelfname),'c')
for key,value in list(shelf.items()):
self.db[key]=value
shelf.close()
log.debug("Syncing new data to disk...")
if hasattr(self.db, "commit"):
self.db.commit()
except:
output.speak("An exception occurred while deshelving the " + application.name + " database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the " + application.name + " developers.",True)
log.exception("Exception while deshelving" + shelfname)
output.speak(_("An exception occurred while saving the {app} database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the {app} developers.").format(app=application.name),True)
log.exception("Exception while saving {}".format(dbname))
os.remove(dbname)
def load_persistent_data(self):
"""Import data from a database file from user config."""
log.debug("Loading storage data...")
dbname=os.path.join(paths.config_path(), str(self.session_id), "cache.db")
# If persist_size is set to 0, we should remove the db file as we are no longer going to save anything.
if self.settings["general"]["persist_size"] == 0:
if os.path.exists(dbname):
os.remove(dbname)
# Let's return from here, as we are not loading anything.
return
# try to load the db file.
try:
os.remove(shelfname)
log.debug("Opening database...")
db=sqlitedict.SqliteDict(os.path.join(paths.config_path(), dbname), 'c')
# If load_cache_in_memory is set to true, we will load the whole database into memory for faster access.
# This is going to be faster when retrieving specific objects, at the cost of more memory.
# Setting this to False will read the objects from database as they are needed, which might be slower for bigger datasets.
if self.settings["general"]["load_cache_in_memory"]:
log.debug("Loading database contents into memory...")
for k in db.keys():
self.db[k] = db[k]
db.commit(blocking=True)
db.close()
log.debug("Contents were loaded successfully.")
else:
log.debug("Instantiating database from disk.")
self.db = db
# We must make sure we won't load more than the amount of buffer specified.
log.debug("Checking if we will load all content...")
for k in self.db.keys():
sized_buffer = self.get_sized_buffer(self.db[k], self.settings["general"]["persist_size"], self.settings["general"]["reverse_timelines"])
self.db[k] = sized_buffer
if self.db.get("cursors") == None:
cursors = dict(direct_messages=-1)
self.db["cursors"] = cursors
except:
output.speak(_("An exception occurred while loading the {app} database. It will be deleted and rebuilt automatically. If this error persists, send the error log to the {app} developers.").format(app=application.name), True)
log.exception("Exception while loading {}".format(dbname))
try:
os.remove(dbname)
except:
pass

View File

@@ -1,11 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from future import standard_library
standard_library.install_aliases()
from builtins import str
from builtins import chr
from builtins import range
import platform
system = platform.system()
from . import utils
@@ -56,29 +49,28 @@ def compose_tweet(tweet, db, relative_times, show_screen_names=False, session=No
else:
text = StripChars(getattr(tweet, value))
if show_screen_names:
user = tweet.user.screen_name
user = session.get_user(tweet.user).screen_name
else:
user = tweet.user.name
user = session.get_user(tweet.user).name
source = re.sub(r"(?s)<.*?>", "", tweet.source)
if hasattr(tweet, "retweeted_status"):
if (hasattr(tweet, "message")) == False and tweet.retweeted_status.is_quote_status == False:
text = "RT @%s: %s" % (tweet.retweeted_status.user.screen_name, text)
elif tweet.retweeted_status.is_quote_status:
if hasattr(tweet, "message") == False and hasattr(tweet.retweeted_status, "is_quote_status") == False:
text = "RT @%s: %s" % (session.get_user(tweet.retweeted_status.user).screen_name, text)
elif hasattr(tweet.retweeted_status, "is_quote_status"):
text = "%s" % (text)
else:
text = "RT @%s: %s" % (tweet.retweeted_status.user.screen_name, text)
text = "RT @%s: %s" % (session.get_user(tweet.retweeted_status.user).screen_name, text)
if not hasattr(tweet, "message"):
if hasattr(tweet, "retweeted_status"):
if hasattr(tweet.retweeted_status, "entities"):
text = utils.expand_urls(text, tweet.retweeted_status.entities)
else:
if hasattr(tweet, "entities"):
text = utils.expand_urls(text, tweet.entities)
if config.app['app-settings']['handle_longtweets']: pass
return [user+", ", text, ts+", ", source]
def compose_direct_message(item, db, relative_times, show_screen_names=False, session=None):
# for a while this function will be together with compose_dm.
# this one composes direct messages based on events (new API Endpoints).
if system == "Windows":
# Let's remove the last 3 digits in the timestamp string.
# Twitter sends their "epoch" timestamp with 3 digits for milliseconds and arrow doesn't like it.
@@ -121,14 +113,14 @@ def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False,
value = "text"
text = StripChars(getattr(quoted_tweet, value))
if show_screen_names:
quoting_user = quoted_tweet.user.screen_name
quoting_user = session.get_user(quoted_tweet.user).screen_name
else:
quoting_user = quoted_tweet.user.name
quoting_user = session.get_user(quoted_tweet.user).name
source = quoted_tweet.source
if hasattr(quoted_tweet, "retweeted_status"):
text = "rt @%s: %s" % (quoted_tweet.retweeted_status.user.screen_name, text)
text = "rt @%s: %s" % (session.get_user(quoted_tweet.retweeted_status.user).screen_name, text)
if text[-1] in chars: text=text+"."
original_user = original_tweet.user.screen_name
original_user = session.get_user(original_tweet.user).screen_name
if hasattr(original_tweet, "message"):
original_text = original_tweet.message
elif hasattr(original_tweet, "full_text"):
@@ -137,6 +129,11 @@ def compose_quoted_tweet(quoted_tweet, original_tweet, show_screen_names=False,
original_text = StripChars(original_tweet.text)
quoted_tweet.message = _(u"{0}. Quoted tweet from @{1}: {2}").format( text, original_user, original_text)
quoted_tweet = tweets.clear_url(quoted_tweet)
if hasattr(original_tweet, "entities") and original_tweet.entities.get("urls"):
if hasattr(quoted_tweet, "entities") == False:
quoted_tweet.entities = {}
if quoted_tweet.entities.get("urls") == None:
quoted_tweet.entities["urls"] = []
quoted_tweet.entities["urls"].extend(original_tweet.entities["urls"])
return quoted_tweet

View File

@@ -0,0 +1,37 @@
# -*- coding: utf-8 -*-
""" Strips unneeded tweet information in order to store tweet objects by using less memory. This is especially useful when buffers start to contain more than a certain amount of items. """
from tweepy.models import Status
def reduce_tweet(tweet):
""" generates a new Tweet model with the fields we currently need, excluding everything else including null values and empty collections. """
allowed_values = ["created_at", "id", "full_text", "text", "message", "in_reply_to_status_id", "in_reply_to_user_id", "is_quote_status", "lang", "source", "coordinates", "quoted_status_id", "extended_entities"]
allowed_entities = ["hashtags", "media", "urls", "user_mentions", "polls"]
status_dict = {}
for key in allowed_values:
if tweet._json.get(key):
status_dict[key] = tweet._json[key]
entities = dict()
for key in allowed_entities:
if tweet._json["entities"].get(key) and tweet._json["entities"].get(key) != None:
entities[key] = tweet._json["entities"][key]
status_dict["entities"] = entities
# If tweet comes from the cached database, it does not include an API, so we can pass None here as we do not use that reference to tweepy's API.
if hasattr(tweet, "_api"):
api = tweet._api
else:
api = None
status = Status().parse(api=api, json=status_dict)
# Quotes and retweets are different objects. So we parse a new tweet when we have a quoted or retweeted status here.
if tweet._json.get("quoted_status"):
quoted_tweet = reduce_tweet(tweet.quoted_status)
status.quoted_status = quoted_tweet
if tweet._json.get("retweeted_status"):
retweeted_tweet = reduce_tweet(tweet.retweeted_status)
status.retweeted_status = retweeted_tweet
# Adds user ID to here so we can reference it later.
# Sometimes, the conversations buffer would send an already reduced tweet here so we will need to return it as is.
if isinstance(tweet.user, str) == False:
status.user = tweet.user.id_str
else:
return tweet
return status

View File

@@ -11,11 +11,13 @@ import application
from pubsub import pub
import tweepy
from tweepy.error import TweepError
from tweepy.models import User as UserModel
from mysc.thread_utils import call_threaded
from keys import keyring
from sessions import base
from sessions.twitter import utils, compose
from sessions.twitter.long_tweets import tweets, twishort
from . import reduce
from .wxUI import authorisationDialog
log = logging.getLogger("sessions.twitterSession")
@@ -37,26 +39,27 @@ class Session(base.baseSession):
self.db[name] = []
if ("users" in self.db) == False:
self.db["users"] = {}
objects = self.db[name]
if ignore_older and len(self.db[name]) > 0:
if self.settings["general"]["reverse_timelines"] == False:
last_id = self.db[name][0].id
else:
last_id = self.db[name][-1].id
self.add_users_from_results(data)
for i in data:
if ignore_older and last_id != None:
if i.id < last_id:
log.error("Ignoring an older tweet... Last id: {0}, tweet id: {1}".format(last_id, i.id))
continue
if utils.find_item(i.id, self.db[name]) == None and utils.is_allowed(i, self.settings, name) == True:
i = self.check_quoted_status(i)
i = self.check_long_tweet(i)
if i == False: continue
if self.settings["general"]["reverse_timelines"] == False: self.db[name].append(i)
else: self.db[name].insert(0, i)
reduced_object = reduce.reduce_tweet(i)
reduced_object = self.check_quoted_status(reduced_object)
reduced_object = self.check_long_tweet(reduced_object)
if self.settings["general"]["reverse_timelines"] == False: objects.append(reduced_object)
else: objects.insert(0, reduced_object)
num = num+1
if hasattr(i, "user"):
if (i.user.id in self.db["users"]) == False:
self.db["users"][i.user.id] = i.user
self.db[name] = objects
return num
def order_people(self, name, data):
@@ -67,11 +70,13 @@ class Session(base.baseSession):
num = 0
if (name in self.db) == False:
self.db[name] = []
objects = self.db[name]
for i in data:
if utils.find_item(i.id, self.db[name]) == None:
if self.settings["general"]["reverse_timelines"] == False: self.db[name].append(i)
else: self.db[name].insert(0, i)
if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
else: objects.insert(0, i)
num = num+1
self.db[name] = objects
return num
def order_direct_messages(self, data):
@@ -82,19 +87,28 @@ class Session(base.baseSession):
sent = 0
if ("direct_messages" in self.db) == False:
self.db["direct_messages"] = []
if ("sent_direct_messages" in self.db) == False:
self.db["sent_direct_messages"] = []
objects = self.db["direct_messages"]
sent_objects = self.db["sent_direct_messages"]
for i in data:
# Twitter returns sender_id as str, which must be converted to int in order to match to our user_id object.
if int(i.message_create["sender_id"]) == self.db["user_id"]:
if "sent_direct_messages" in self.db and utils.find_item(i.id, self.db["sent_direct_messages"]) == None:
if self.settings["general"]["reverse_timelines"] == False: self.db["sent_direct_messages"].append(i)
else: self.db["sent_direct_messages"].insert(0, i)
if self.settings["general"]["reverse_timelines"] == False: sent_objects.append(i)
else: sent_objects.insert(0, i)
sent = sent+1
else:
if utils.find_item(i.id, self.db["direct_messages"]) == None:
if self.settings["general"]["reverse_timelines"] == False: self.db["direct_messages"].append(i)
else: self.db["direct_messages"].insert(0, i)
if self.settings["general"]["reverse_timelines"] == False: objects.append(i)
else: objects.insert(0, i)
incoming = incoming+1
self.db["direct_messages"] = objects
self.db["sent_direct_messages"] = sent_objects
pub.sendMessage("sent-dms-updated", total=sent, account=self.db["user_name"])
return incoming
def __init__(self, *args, **kwargs):
@@ -105,6 +119,12 @@ class Session(base.baseSession):
self.reconnection_function_active = False
self.counter = 0
self.lists = []
# As users are cached for accessing them with not too many twitter calls,
# there could be a weird situation where a deleted user who sent direct messages to the current account will not be able to be retrieved at twitter.
# So we need to store an "user deleted" object in the cache, but have the ID of the deleted user in a local reference.
# This will be especially useful because if the user reactivates their account later, TWblue will try to retrieve such user again at startup.
# If we wouldn't implement this approach, TWBlue would save permanently the "deleted user" object.
self.deleted_users = {}
# @_require_configuration
def login(self, verify_credentials=True):
@@ -160,35 +180,6 @@ class Session(base.baseSession):
self.verify_authorisation(pincode)
self.authorisation_dialog.Destroy()
def get_more_items(self, update_function, users=False, dm=False, name=None, *args, **kwargs):
""" Get more items for twitter objects.
update_function str: function to call for getting more items. Must be member of self.twitter.
users, dm bool: If any of these is set to True, the function will treat items as users or dm (they need different handling).
name str: name of the database item to put new element in."""
results = []
if "cursor" in kwargs and kwargs["cursor"] == 0:
output.speak(_(u"There are no more items to retrieve in this buffer."))
return
data = getattr(self.twitter, update_function)(*args, **kwargs)
if users == True:
if type(data) == dict and "next_cursor" in data:
if "next_cursor" in data: # There are more objects to retrieve.
self.db[name]["cursor"] = data["next_cursor"]
else: # Set cursor to 0, wich means no more items available.
self.db[name]["cursor"] = 0
for i in data["users"]: results.append(i)
elif type(data) == list:
results.extend(data[1:])
elif dm == True:
if "next_cursor" in data: # There are more objects to retrieve.
self.db[name]["cursor"] = data["next_cursor"]
else: # Set cursor to 0, wich means no more items available.
self.db[name]["cursor"] = 0
for i in data["events"]: results.append(i)
else:
results.extend(data[1:])
return results
def api_call(self, call_name, action="", _sound=None, report_success=False, report_failure=True, preexec_message="", *args, **kwargs):
""" Make a call to the Twitter API. If there is a connectionError or another exception not related to Twitter, It will call the method again at least 25 times, waiting a while between calls. Useful for post methods.
If twitter returns an error, it will not call the method anymore.
@@ -358,10 +349,11 @@ class Session(base.baseSession):
value = "full_text"
else:
value = "text"
if hasattr(quoted_tweet, "entities"):
setattr(quoted_tweet, value, utils.expand_urls(getattr(quoted_tweet, value), quoted_tweet.entities))
if quoted_tweet.is_quote_status == True and hasattr(quoted_tweet, "quoted_status"):
if hasattr(quoted_tweet, "is_quote_status") == True and hasattr(quoted_tweet, "quoted_status"):
original_tweet = quoted_tweet.quoted_status
elif hasattr(quoted_tweet, "retweeted_status") and quoted_tweet.retweeted_status.is_quote_status == True and hasattr(quoted_tweet.retweeted_status, "quoted_status"):
elif hasattr(quoted_tweet, "retweeted_status") and hasattr(quoted_tweet.retweeted_status, "is_quote_status") == True and hasattr(quoted_tweet.retweeted_status, "quoted_status"):
original_tweet = quoted_tweet.retweeted_status.quoted_status
else:
return quoted_tweet
@@ -372,13 +364,17 @@ class Session(base.baseSession):
value = "message"
else:
value = "text"
if hasattr(original_tweet, "entities"):
setattr(original_tweet, value, utils.expand_urls(getattr(original_tweet, value), original_tweet.entities))
return compose.compose_quoted_tweet(quoted_tweet, original_tweet)
# ToDo: Shall we check whether we should add show_screen_names here?
return compose.compose_quoted_tweet(quoted_tweet, original_tweet, session=self)
def check_long_tweet(self, tweet):
""" Process a tweet and add extra info if it's a long tweet made with Twyshort.
tweet dict: a tweet object.
returns a tweet with a new argument message, or original tweet if it's not a long tweet."""
long = False
if hasattr(tweet, "entities") and tweet.entities.get("urls"):
long = twishort.is_long(tweet)
if long != False and config.app["app-settings"]["handle_longtweets"]:
message = twishort.get_full_text(long)
@@ -386,35 +382,56 @@ class Session(base.baseSession):
tweet.quoted_status.message = message
if tweet.quoted_status.message == False: return False
tweet.quoted_status.twishort = True
if hasattr(tweet.quoted_status, "entities") and tweet.quoted_status.entities.get("user_mentions"):
for i in tweet.quoted_status.entities["user_mentions"]:
if "@%s" % (i["screen_name"]) not in tweet.quoted_status.message and i["screen_name"] != tweet.user.screen_name:
if hasattr(tweet.quoted_status, "retweeted_status") and tweet.retweeted_status.user.screen_name == i["screen_name"]:
if "@%s" % (i["screen_name"]) not in tweet.quoted_status.message and i["screen_name"] != self.get_user(tweet.user).screen_name:
if hasattr(tweet.quoted_status, "retweeted_status") and self.get_user(tweet.retweeted_status.user).screen_name == i["screen_name"]:
continue
tweet.quoted_status.message = u"@%s %s" % (i["screen_name"], tweet.message)
else:
tweet.message = message
if tweet.message == False: return False
tweet.twishort = True
if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
for i in tweet.entities["user_mentions"]:
if "@%s" % (i["screen_name"]) not in tweet.message and i["screen_name"] != tweet.user.screen_name:
if hasattr(tweet, "retweeted_status") and tweet.retweeted_status.user.screen_name == i["screen_name"]:
if "@%s" % (i["screen_name"]) not in tweet.message and i["screen_name"] != self.get_user(tweet.user).screen_name:
if hasattr(tweet, "retweeted_status") and self.get_user(tweet.retweeted_status.user).screen_name == i["screen_name"]:
continue
tweet.message = u"@%s %s" % (i["screen_name"], tweet.message)
return tweet
def get_user(self, id):
""" Returns an user object associated with an ID.
id str: User identifier, provided by Twitter.
returns an user dict."""
if ("users" in self.db) == False or (id in self.db["users"]) == False:
returns a tweepy user object."""
if hasattr(id, "id_str"):
log.error("Called get_user function by passing a full user id as a parameter.")
id = id.id_str
# Check if the user has been added to the list of deleted users previously.
if id in self.deleted_users:
log.debug("Returning user {} from the list of deleted users.".format(id))
return self.deleted_users[id]
if ("users" in self.db) == False or (str(id) in self.db["users"]) == False:
log.debug("Requesting user id {} as it is not present in the users database.".format(id))
try:
user = self.twitter.get_user(id=id)
except TweepError as err:
user = dict(screen_name="deleted_account", name="Deleted account")
return user
self.db["users"][user.id_str] = user
user = UserModel(None)
user.screen_name = "deleted_user"
user.id = id
user.name = _("Deleted account")
if hasattr(err, "api_code") and err.api_code == 50:
self.deleted_users[id] = user
return user
else:
return self.db["users"][id]
log.exception("Error when attempting to retrieve an user from Twitter.")
return user
users = self.db["users"]
users[user.id_str] = user
self.db["users"] = users
return user
else:
return self.db["users"][str(id)]
def get_user_by_screen_name(self, screen_name):
""" Returns an user identifier associated with a screen_name.
@@ -422,28 +439,65 @@ class Session(base.baseSession):
returns an user ID."""
if ("users" in self.db) == False:
user = utils.if_user_exists(self.twitter, screen_name)
self.db["users"][user["id_str"]] = user
return user["id_str"]
users = self.db["users"]
users[user["id"]] = user
self.db["users"] = users
return user["id"]
else:
for i in list(self.db["users"].keys()):
if self.db["users"][i].screen_name == screen_name:
return self.db["users"][i].id_str
return self.db["users"][i].id
user = utils.if_user_exists(self.twitter, screen_name)
self.db["users"][user.id_str] = user
return user.id_str
users = self.db["users"]
users[user.id] = user
self.db["users"] = users
return user.id
def save_users(self, user_ids):
""" Adds all new users to the users database. """
if len(user_ids) == 0:
return
log.debug("Received %d user IDS to be added in the database." % (len(user_ids)))
users_to_retrieve = [user_id for user_id in user_ids if user_id not in self.db["users"]]
users_to_retrieve = [user_id for user_id in user_ids if (user_id not in self.db["users"] and user_id not in self.deleted_users)]
# Remove duplicates
users_to_retrieve = list(dict.fromkeys(users_to_retrieve))
if len(users_to_retrieve) == 0:
return
log.debug("TWBlue will get %d new users from Twitter." % (len(users_to_retrieve)))
try:
users = self.twitter.lookup_users(user_ids=users_to_retrieve, tweet_mode="extended")
users_db = self.db["users"]
for user in users:
self.db["users"][user.id_str] = user
users_db[user.id_str] = user
log.debug("Added %d new users" % (len(users)))
self.db["users"] = users_db
except TweepError as err:
if hasattr(err, "api_code") and err.api_code == 17: # Users not found.
log.error("The specified users {} were not found in twitter.".format(user_ids))
# Creates a deleted user object for every user_id not found here.
# This will make TWBlue to not waste Twitter API calls when attempting to retrieve those users again.
# As deleted_users is not saved across restarts, when restarting TWBlue, it will retrieve the correct users if they enabled their accounts.
for id in users_to_retrieve:
user = UserModel(None)
user.screen_name = "deleted_user"
user.id = id
user.name = _("Deleted account")
self.deleted_users[id] = user
else:
log.exception("An exception happened while attempting to retrieve a list of users from direct messages in Twitter.")
def add_users_from_results(self, data):
users = self.db["users"]
for i in data:
if hasattr(i, "user"):
if isinstance(i.user, str):
log.warning("A String was passed to be added as an user. This is normal only if TWBlue tried to load a conversation.")
continue
if (i.user.id_str in self.db["users"]) == False:
users[i.user.id_str] = i.user
if hasattr(i, "quoted_status") and (i.quoted_status.user.id_str in self.db["users"]) == False:
users[i.quoted_status.user.id_str] = i.quoted_status.user
if hasattr(i, "retweeted_status") and (i.retweeted_status.user.id_str in self.db["users"]) == False:
users[i.retweeted_status.user.id_str] = i.retweeted_status.user
self.db["users"] = users

View File

@@ -21,28 +21,29 @@ bad_chars = '\'\\\n.,[](){}:;"'
def find_urls_in_text(text):
return url_re2.findall(text)
def find_urls (tweet):
def find_urls (tweet, twitter_media=False):
urls = []
if twitter_media and hasattr(tweet, "extended_entities"):
for mediaItem in tweet.extended_entities["media"]:
if mediaItem["type"] == "video":
for variant in mediaItem["video_info"]["variants"]:
if variant["content_type"] == "video/mp4":
urls.append(variant["url"])
break
# Let's add URLS from tweet entities.
if hasattr(tweet, "message_create"):
entities = tweet.message_create["message_data"]["entities"]
else:
if hasattr(tweet, "entities") == True:
entities = tweet.entities
if entities.get("urls") != None:
for i in entities["urls"]:
if i["expanded_url"] not in urls:
urls.append(i["expanded_url"])
if hasattr(tweet, "quoted_status"):
for i in tweet.quoted_status.entities["urls"]:
if i["expanded_url"] not in urls:
urls.append(i["expanded_url"])
urls.extend(find_urls(tweet.quoted_status))
if hasattr(tweet, "retweeted_status"):
for i in tweet.retweeted_status.entities["urls"]:
if i["expanded_url"] not in urls:
urls.append(i["expanded_url"])
if hasattr(tweet["retweeted_status"], "quoted_status"):
for i in tweet.retweeted_status.quoted_status.entities["urls"]:
if i["expanded_url"] not in urls:
urls.append(i["expanded_url"])
urls.extend(find_urls(tweet.retweeted_status))
if hasattr(tweet, "message"):
i = "message"
elif hasattr(tweet, "full_text"):
@@ -69,19 +70,25 @@ def find_list(name, lists):
if lists[i].name == name: return lists[i].id
def is_audio(tweet):
# Checks firstly for Twitter videos and audios.
if hasattr(tweet, "extended_entities"):
for mediaItem in tweet.extended_entities["media"]:
if mediaItem["type"] == "video":
return True
try:
if len(find_urls(tweet)) < 1:
return False
if hasattr(tweet, "message_create"):
entities = tweet.message_create["message_data"]["entities"]
else:
if hasattr(tweet, "entities") == False or tweet.entities.get("hashtags") == None:
return False
entities = tweet.entities
if len(entities["hashtags"]) > 0:
for i in entities["hashtags"]:
if i["text"] == "audio":
return True
except IndexError:
print(tweet.entities["hashtags"])
log.exception("Exception while executing is_audio hashtag algorithm")
def is_geocoded(tweet):
@@ -92,6 +99,8 @@ def is_media(tweet):
if hasattr(tweet, "message_create"):
entities = tweet.message_create["message_data"]["entities"]
else:
if hasattr(tweet, "entities") == False or tweet.entities.get("hashtags") == None:
return False
entities = tweet.entities
if entities.get("media") == None:
return False
@@ -103,28 +112,29 @@ def is_media(tweet):
def get_all_mentioned(tweet, conf, field="screen_name"):
""" Gets all users that have been mentioned."""
results = []
if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
for i in tweet.entities["user_mentions"]:
if i["screen_name"] != conf["user_name"] and i["screen_name"] != tweet.user.screen_name:
if i["screen_name"] != conf["user_name"] and i["id_str"] != tweet.user:
if i.get(field) not in results:
results.append(i.get(field))
return results
def get_all_users(tweet, conf):
def get_all_users(tweet, session):
string = []
user = session.get_user(tweet.user)
if hasattr(tweet, "retweeted_status"):
string.append(tweet.user.screen_name)
string.append(user.screen_name)
tweet = tweet.retweeted_status
if hasattr(tweet, "sender"):
string.append(tweet.sender.screen_name)
else:
if tweet.user.screen_name != conf["user_name"]:
string.append(tweet.user.screen_name)
if user.screen_name != session.db["user_name"]:
string.append(user.screen_name)
if hasattr(tweet, "entities") and tweet.entities.get("user_mentions"):
for i in tweet.entities["user_mentions"]:
if i["screen_name"] != conf["user_name"] and i["screen_name"] != tweet.user.screen_name:
if i["screen_name"] != session.db["user_name"] and i["screen_name"] != user.screen_name:
if i["screen_name"] not in string:
string.append(i["screen_name"])
if len(string) == 0:
string.append(tweet.user.screen_name)
string.append(user.screen_name)
return string
def if_user_exists(twitter, user):
@@ -144,7 +154,7 @@ def is_allowed(tweet, settings, buffer_name):
tweet_data = {}
if hasattr(tweet, "retweeted_status"):
tweet_data["retweet"] = True
if tweet.in_reply_to_status_id_str != None:
if tweet.in_reply_to_status_id != None:
tweet_data["reply"] = True
if hasattr(tweet, "quoted_status"):
tweet_data["quote"] = True
@@ -209,6 +219,8 @@ def twitter_error(error):
def expand_urls(text, entities):
""" Expand all URLS present in text with information found in entities"""
if entities.get("urls") == None:
return text
urls = find_urls_in_text(text)
for url in entities["urls"]:
if url["url"] in text:

1
src/test/__init__.py Normal file
View File

@@ -0,0 +1 @@
# -*- coding: utf-8 -*-

200
src/test/test_cache.py Normal file
View File

@@ -0,0 +1,200 @@
# -*- coding: utf-8 -*-
""" Test case to check some of the scenarios we might face when storing tweets in cache, both loading into memory or rreading from disk. """
import unittest
import os
import paths
import sqlitedict
import shutil
# The base session module requires sound as a dependency, and this needs libVLC to be locatable.
os.environ['PYTHON_VLC_MODULE_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", "x86"))
os.environ['PYTHON_VLC_LIB_PATH']=os.path.abspath(os.path.join(paths.app_path(), "..", "windows-dependencies", "x86", "libvlc.dll"))
from sessions import base
class cacheTestCase(unittest.TestCase):
def setUp(self):
""" Configures a fake session to check caching objects here. """
self.session = base.baseSession("testing")
if os.path.exists(os.path.join(paths.config_path(), "testing")) == False:
os.mkdir(os.path.join(paths.config_path(), "testing"))
self.session.get_configuration()
def tearDown(self):
""" Removes the previously configured session. """
session_folder = os.path.join(paths.config_path(), "testing")
if os.path.exists(session_folder):
shutil.rmtree(session_folder)
def generate_dataset(self):
""" Generates a sample dataset"""
dataset = dict(home_timeline=["message" for i in range(10000)], mentions_timeline=["mention" for i in range(20000)])
return dataset
### Testing database being read from disk.
def test_cache_in_disk_unlimited_size(self):
""" Tests cache database being read from disk, storing the whole datasets. """
dataset = self.generate_dataset()
self.session.settings["general"]["load_cache_in_memory"] = False
self.session.settings["general"]["persist_size"] = -1
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
self.session.save_persistent_data()
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(len(self.session.db.get("home_timeline")), 10000)
self.assertEquals(len(self.session.db.get("mentions_timeline")), 20000)
self.session.db.close()
def test_cache_in_disk_limited_dataset(self):
""" Tests wether the cache stores only the amount of items we ask it to store. """
dataset = self.generate_dataset()
self.session.settings["general"]["load_cache_in_memory"] = False
self.session.settings["general"]["persist_size"] = 100
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
# Might cause an out of sync error between the GUI lists and the database.
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(len(self.session.db.get("home_timeline")), 100)
self.assertEquals(len(self.session.db.get("mentions_timeline")), 100)
self.session.db.close()
def test_cache_in_disk_limited_dataset_unreversed(self):
"""Test if the cache is saved properly in unreversed buffers, when newest items are at the end of the list. """
dataset = dict(home_timeline=[i for i in range(20)], mentions_timeline=[i for i in range(20)])
self.session.settings["general"]["load_cache_in_memory"] = False
self.session.settings["general"]["persist_size"] = 10
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
# Might cause an out of sync error between the GUI lists and the database.
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(self.session.db.get("home_timeline")[0], 10)
self.assertEquals(self.session.db.get("mentions_timeline")[0], 10)
self.assertEquals(self.session.db.get("home_timeline")[-1], 19)
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 19)
self.session.db.close()
def test_cache_in_disk_limited_dataset_reversed(self):
"""Test if the cache is saved properly in reversed buffers, when newest items are at the start of the list. """
dataset = dict(home_timeline=[i for i in range(19, -1, -1)], mentions_timeline=[i for i in range(19, -1, -1)])
self.session.settings["general"]["load_cache_in_memory"] = False
self.session.settings["general"]["persist_size"] = 10
self.session.settings["general"]["reverse_timelines"] = True
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
# We need to save and load the db again because we cannot modify buffers' size while the database is opened.
# As TWBlue reads directly from db when reading from disk, an attempt to modify buffers size while Blue is reading the db
# Might cause an out of sync error between the GUI lists and the database.
# So we perform the changes to buffer size when loading data during app startup if the DB is read from disk.
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, sqlitedict.SqliteDict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(self.session.db.get("home_timeline")[0], 19)
self.assertEquals(self.session.db.get("mentions_timeline")[0], 19)
self.assertEquals(self.session.db.get("home_timeline")[-1], 10)
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 10)
self.session.db.close()
### Testing database being loaded into memory. Those tests should give the same results than before
### but as we have different code depending whether we load db into memory or read it from disk,
### We need to test this anyways.
def test_cache_in_memory_unlimited_size(self):
""" Tests cache database being loaded in memory, storing the whole datasets. """
dataset = self.generate_dataset()
self.session.settings["general"]["load_cache_in_memory"] = True
self.session.settings["general"]["persist_size"] = -1
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, dict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(len(self.session.db.get("home_timeline")), 10000)
self.assertEquals(len(self.session.db.get("mentions_timeline")), 20000)
def test_cache_in_memory_limited_dataset(self):
""" Tests wether the cache stores only the amount of items we ask it to store, when loaded in memory. """
dataset = self.generate_dataset()
self.session.settings["general"]["load_cache_in_memory"] = True
self.session.settings["general"]["persist_size"] = 100
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, dict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(len(self.session.db.get("home_timeline")), 100)
self.assertEquals(len(self.session.db.get("mentions_timeline")), 100)
def test_cache_in_memory_limited_dataset_unreversed(self):
"""Test if the cache is saved properly when loaded in memory in unreversed buffers, when newest items are at the end of the list. """
dataset = dict(home_timeline=[i for i in range(20)], mentions_timeline=[i for i in range(20)])
self.session.settings["general"]["load_cache_in_memory"] = True
self.session.settings["general"]["persist_size"] = 10
self.session.load_persistent_data()
self.assertTrue(len(self.session.db)==1)
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, dict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(self.session.db.get("home_timeline")[0], 10)
self.assertEquals(self.session.db.get("mentions_timeline")[0], 10)
self.assertEquals(self.session.db.get("home_timeline")[-1], 19)
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 19)
def test_cache_in_memory_limited_dataset_reversed(self):
"""Test if the cache is saved properly in reversed buffers, when newest items are at the start of the list. This test if for db read into memory. """
dataset = dict(home_timeline=[i for i in range(19, -1, -1)], mentions_timeline=[i for i in range(19, -1, -1)])
self.session.settings["general"]["load_cache_in_memory"] = True
self.session.settings["general"]["persist_size"] = 10
self.session.settings["general"]["reverse_timelines"] = True
self.session.load_persistent_data()
self.session.db["home_timeline"] = dataset["home_timeline"]
self.session.db["mentions_timeline"] = dataset["mentions_timeline"]
self.session.save_persistent_data()
self.session.db = dict()
self.session.load_persistent_data()
self.assertIsInstance(self.session.db, dict)
self.assertTrue(self.session.db.get("home_timeline") != None)
self.assertTrue(self.session.db.get("mentions_timeline") != None)
self.assertEquals(self.session.db.get("home_timeline")[0], 19)
self.assertEquals(self.session.db.get("mentions_timeline")[0], 19)
self.assertEquals(self.session.db.get("home_timeline")[-1], 10)
self.assertEquals(self.session.db.get("mentions_timeline")[-1], 10)
if __name__ == "__main__":
unittest.main()

View File

@@ -1,7 +1,4 @@
# -*- coding: utf-8 -*-
from __future__ import absolute_import
from __future__ import unicode_literals
from builtins import range
import logging as original_logger
import wx
import application
@@ -127,6 +124,7 @@ class generalAccount(wx.Panel, baseDialog.BaseWXDialog):
self.persist_size = wx.TextCtrl(self, -1)
sizer.Add(PersistSizeLabel, 0, wx.ALL, 5)
sizer.Add(self.persist_size, 0, wx.ALL, 5)
self.load_cache_in_memory = wx.CheckBox(self, wx.NewId(), _("Load cache for tweets in memory (much faster in big datasets but requires more RAM)"))
self.SetSizer(sizer)
class reporting(wx.Panel, baseDialog.BaseWXDialog):

View File

@@ -221,7 +221,7 @@ class dm(textLimited):
def __init__(self, title, message, users, *args, **kwargs):
super(dm, self).__init__()
self.createControls(message, title, users)
self.createControls(title, message, users)
# self.onTimer(wx.EVT_CHAR_HOOK)
# self.SetClientSize(self.mainBox.CalcMin())

View File

@@ -1,4 +1,4 @@
{"current_version": "5",
{"current_version": "6",
"description": "Snapshot version.",
"date": "unknown",
"downloads":