forked from indymedia/epicyon
Avoid race condition when repeating post
parent
ec07f83e5a
commit
51f2b060c9
136
announce.py
136
announce.py
|
@ -6,18 +6,15 @@ __maintainer__ = "Bob Mottram"
|
||||||
__email__ = "bob@freedombone.net"
|
__email__ = "bob@freedombone.net"
|
||||||
__status__ = "Production"
|
__status__ = "Production"
|
||||||
|
|
||||||
import os
|
|
||||||
from pprint import pprint
|
|
||||||
from utils import removePostFromCache
|
|
||||||
from utils import getStatusNumber
|
from utils import getStatusNumber
|
||||||
from utils import createOutboxDir
|
from utils import createOutboxDir
|
||||||
from utils import urlPermitted
|
from utils import urlPermitted
|
||||||
from utils import getNicknameFromActor
|
from utils import getNicknameFromActor
|
||||||
from utils import getDomainFromActor
|
from utils import getDomainFromActor
|
||||||
from utils import locatePost
|
from utils import locatePost
|
||||||
from utils import getCachedPostFilename
|
|
||||||
from utils import loadJson
|
|
||||||
from utils import saveJson
|
from utils import saveJson
|
||||||
|
from utils import undoAnnounceCollectionEntry
|
||||||
|
from utils import updateAnnounceCollection
|
||||||
from posts import sendSignedJson
|
from posts import sendSignedJson
|
||||||
from posts import getPersonBox
|
from posts import getPersonBox
|
||||||
from session import postJson
|
from session import postJson
|
||||||
|
@ -74,135 +71,6 @@ def outboxAnnounce(recentPostsCache: {},
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
def undoAnnounceCollectionEntry(recentPostsCache: {},
|
|
||||||
baseDir: str, postFilename: str,
|
|
||||||
actor: str, domain: str, debug: bool) -> None:
|
|
||||||
"""Undoes an announce for a particular actor by removing it from
|
|
||||||
the "shares" collection within a post. Note that the "shares"
|
|
||||||
collection has no relation to shared items in shares.py. It's
|
|
||||||
shares of posts, not shares of physical objects.
|
|
||||||
"""
|
|
||||||
postJsonObject = loadJson(postFilename)
|
|
||||||
if postJsonObject:
|
|
||||||
# remove any cached version of this announce so that the announce
|
|
||||||
# icon is changed
|
|
||||||
nickname = getNicknameFromActor(actor)
|
|
||||||
cachedPostFilename = getCachedPostFilename(baseDir, nickname, domain,
|
|
||||||
postJsonObject)
|
|
||||||
if cachedPostFilename:
|
|
||||||
if os.path.isfile(cachedPostFilename):
|
|
||||||
os.remove(cachedPostFilename)
|
|
||||||
removePostFromCache(postJsonObject, recentPostsCache)
|
|
||||||
|
|
||||||
if not postJsonObject.get('type'):
|
|
||||||
return
|
|
||||||
if postJsonObject['type'] != 'Create':
|
|
||||||
return
|
|
||||||
if not postJsonObject.get('object'):
|
|
||||||
if debug:
|
|
||||||
pprint(postJsonObject)
|
|
||||||
print('DEBUG: post has no object')
|
|
||||||
return
|
|
||||||
if not isinstance(postJsonObject['object'], dict):
|
|
||||||
return
|
|
||||||
if not postJsonObject['object'].get('shares'):
|
|
||||||
return
|
|
||||||
if not postJsonObject['object']['shares'].get('items'):
|
|
||||||
return
|
|
||||||
totalItems = 0
|
|
||||||
if postJsonObject['object']['shares'].get('totalItems'):
|
|
||||||
totalItems = postJsonObject['object']['shares']['totalItems']
|
|
||||||
itemFound = False
|
|
||||||
for announceItem in postJsonObject['object']['shares']['items']:
|
|
||||||
if announceItem.get('actor'):
|
|
||||||
if announceItem['actor'] == actor:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: Announce was removed for ' + actor)
|
|
||||||
anIt = announceItem
|
|
||||||
postJsonObject['object']['shares']['items'].remove(anIt)
|
|
||||||
itemFound = True
|
|
||||||
break
|
|
||||||
if itemFound:
|
|
||||||
if totalItems == 1:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: shares (announcements) ' +
|
|
||||||
'was removed from post')
|
|
||||||
del postJsonObject['object']['shares']
|
|
||||||
else:
|
|
||||||
itlen = len(postJsonObject['object']['shares']['items'])
|
|
||||||
postJsonObject['object']['shares']['totalItems'] = itlen
|
|
||||||
|
|
||||||
saveJson(postJsonObject, postFilename)
|
|
||||||
|
|
||||||
|
|
||||||
def updateAnnounceCollection(recentPostsCache: {},
|
|
||||||
baseDir: str, postFilename: str,
|
|
||||||
actor: str, domain: str, debug: bool) -> None:
|
|
||||||
"""Updates the announcements collection within a post
|
|
||||||
Confusingly this is known as "shares", but isn't the
|
|
||||||
same as shared items within shares.py
|
|
||||||
It's shares of posts, not shares of physical objects.
|
|
||||||
"""
|
|
||||||
postJsonObject = loadJson(postFilename)
|
|
||||||
if postJsonObject:
|
|
||||||
# remove any cached version of this announce so that the announce
|
|
||||||
# icon is changed
|
|
||||||
nickname = getNicknameFromActor(actor)
|
|
||||||
cachedPostFilename = getCachedPostFilename(baseDir, nickname, domain,
|
|
||||||
postJsonObject)
|
|
||||||
if cachedPostFilename:
|
|
||||||
if os.path.isfile(cachedPostFilename):
|
|
||||||
os.remove(cachedPostFilename)
|
|
||||||
removePostFromCache(postJsonObject, recentPostsCache)
|
|
||||||
|
|
||||||
if not postJsonObject.get('object'):
|
|
||||||
if debug:
|
|
||||||
pprint(postJsonObject)
|
|
||||||
print('DEBUG: post ' + postFilename + ' has no object')
|
|
||||||
return
|
|
||||||
if not isinstance(postJsonObject['object'], dict):
|
|
||||||
return
|
|
||||||
postUrl = postJsonObject['id'].replace('/activity', '') + '/shares'
|
|
||||||
if not postJsonObject['object'].get('shares'):
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: Adding initial shares (announcements) to ' +
|
|
||||||
postUrl)
|
|
||||||
announcementsJson = {
|
|
||||||
"@context": "https://www.w3.org/ns/activitystreams",
|
|
||||||
'id': postUrl,
|
|
||||||
'type': 'Collection',
|
|
||||||
"totalItems": 1,
|
|
||||||
'items': [{
|
|
||||||
'type': 'Announce',
|
|
||||||
'actor': actor
|
|
||||||
}]
|
|
||||||
}
|
|
||||||
postJsonObject['object']['shares'] = announcementsJson
|
|
||||||
else:
|
|
||||||
if postJsonObject['object']['shares'].get('items'):
|
|
||||||
sharesItems = postJsonObject['object']['shares']['items']
|
|
||||||
for announceItem in sharesItems:
|
|
||||||
if announceItem.get('actor'):
|
|
||||||
if announceItem['actor'] == actor:
|
|
||||||
return
|
|
||||||
newAnnounce = {
|
|
||||||
'type': 'Announce',
|
|
||||||
'actor': actor
|
|
||||||
}
|
|
||||||
postJsonObject['object']['shares']['items'].append(newAnnounce)
|
|
||||||
itlen = len(postJsonObject['object']['shares']['items'])
|
|
||||||
postJsonObject['object']['shares']['totalItems'] = itlen
|
|
||||||
else:
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: shares (announcements) section of post ' +
|
|
||||||
'has no items list')
|
|
||||||
|
|
||||||
if debug:
|
|
||||||
print('DEBUG: saving post with shares (announcements) added')
|
|
||||||
pprint(postJsonObject)
|
|
||||||
saveJson(postJsonObject, postFilename)
|
|
||||||
|
|
||||||
|
|
||||||
def announcedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
|
def announcedByPerson(postJsonObject: {}, nickname: str, domain: str) -> bool:
|
||||||
"""Returns True if the given post is announced by the given person
|
"""Returns True if the given post is announced by the given person
|
||||||
"""
|
"""
|
||||||
|
|
26
daemon.py
26
daemon.py
|
@ -143,6 +143,8 @@ from shares import getSharesFeedForPerson
|
||||||
from shares import addShare
|
from shares import addShare
|
||||||
from shares import removeShare
|
from shares import removeShare
|
||||||
from shares import expireShares
|
from shares import expireShares
|
||||||
|
from utils import undoAnnounceCollectionEntry
|
||||||
|
from utils import updateAnnounceCollection
|
||||||
from utils import updateLikesCollection
|
from utils import updateLikesCollection
|
||||||
from utils import undoLikesCollectionEntry
|
from utils import undoLikesCollectionEntry
|
||||||
from utils import deletePost
|
from utils import deletePost
|
||||||
|
@ -2275,6 +2277,18 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
self.server.debug,
|
self.server.debug,
|
||||||
self.server.projectVersion)
|
self.server.projectVersion)
|
||||||
if announceJson:
|
if announceJson:
|
||||||
|
# directly announce the post file
|
||||||
|
announcePostFilename = \
|
||||||
|
locatePost(self.server.baseDir,
|
||||||
|
self.postToNickname,
|
||||||
|
self.server.domain,
|
||||||
|
repeatUrl)
|
||||||
|
if announcePostFilename:
|
||||||
|
updateAnnounceCollection(self.server.recentPostsCache,
|
||||||
|
self.server.baseDir,
|
||||||
|
announcePostFilename,
|
||||||
|
actor, self.server.domain,
|
||||||
|
self.server.debug)
|
||||||
self._postToOutboxThread(announceJson)
|
self._postToOutboxThread(announceJson)
|
||||||
self.server.GETbusy = False
|
self.server.GETbusy = False
|
||||||
actorAbsolute = self.server.httpPrefix + '://' + \
|
actorAbsolute = self.server.httpPrefix + '://' + \
|
||||||
|
@ -2360,6 +2374,18 @@ class PubServer(BaseHTTPRequestHandler):
|
||||||
'type': 'Announce'
|
'type': 'Announce'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
# directly undo announce in post file
|
||||||
|
announcePostFilename = \
|
||||||
|
locatePost(self.server.baseDir,
|
||||||
|
self.postToNickname,
|
||||||
|
self.server.domain,
|
||||||
|
repeatUrl)
|
||||||
|
if announcePostFilename:
|
||||||
|
undoAnnounceCollectionEntry(self.server.recentPostsCache,
|
||||||
|
self.server.baseDir,
|
||||||
|
announcePostFilename,
|
||||||
|
actor, self.server.domain,
|
||||||
|
self.server.debug)
|
||||||
self._postToOutboxThread(newUndoAnnounce)
|
self._postToOutboxThread(newUndoAnnounce)
|
||||||
self.server.GETbusy = False
|
self.server.GETbusy = False
|
||||||
actorAbsolute = self.server.httpPrefix + '://' + \
|
actorAbsolute = self.server.httpPrefix + '://' + \
|
||||||
|
|
129
utils.py
129
utils.py
|
@ -923,3 +923,132 @@ def updateLikesCollection(recentPostsCache: {},
|
||||||
print('DEBUG: saving post with likes added')
|
print('DEBUG: saving post with likes added')
|
||||||
pprint(postJsonObject)
|
pprint(postJsonObject)
|
||||||
saveJson(postJsonObject, postFilename)
|
saveJson(postJsonObject, postFilename)
|
||||||
|
|
||||||
|
|
||||||
|
def undoAnnounceCollectionEntry(recentPostsCache: {},
|
||||||
|
baseDir: str, postFilename: str,
|
||||||
|
actor: str, domain: str, debug: bool) -> None:
|
||||||
|
"""Undoes an announce for a particular actor by removing it from
|
||||||
|
the "shares" collection within a post. Note that the "shares"
|
||||||
|
collection has no relation to shared items in shares.py. It's
|
||||||
|
shares of posts, not shares of physical objects.
|
||||||
|
"""
|
||||||
|
postJsonObject = loadJson(postFilename)
|
||||||
|
if postJsonObject:
|
||||||
|
# remove any cached version of this announce so that the announce
|
||||||
|
# icon is changed
|
||||||
|
nickname = getNicknameFromActor(actor)
|
||||||
|
cachedPostFilename = getCachedPostFilename(baseDir, nickname, domain,
|
||||||
|
postJsonObject)
|
||||||
|
if cachedPostFilename:
|
||||||
|
if os.path.isfile(cachedPostFilename):
|
||||||
|
os.remove(cachedPostFilename)
|
||||||
|
removePostFromCache(postJsonObject, recentPostsCache)
|
||||||
|
|
||||||
|
if not postJsonObject.get('type'):
|
||||||
|
return
|
||||||
|
if postJsonObject['type'] != 'Create':
|
||||||
|
return
|
||||||
|
if not postJsonObject.get('object'):
|
||||||
|
if debug:
|
||||||
|
pprint(postJsonObject)
|
||||||
|
print('DEBUG: post has no object')
|
||||||
|
return
|
||||||
|
if not isinstance(postJsonObject['object'], dict):
|
||||||
|
return
|
||||||
|
if not postJsonObject['object'].get('shares'):
|
||||||
|
return
|
||||||
|
if not postJsonObject['object']['shares'].get('items'):
|
||||||
|
return
|
||||||
|
totalItems = 0
|
||||||
|
if postJsonObject['object']['shares'].get('totalItems'):
|
||||||
|
totalItems = postJsonObject['object']['shares']['totalItems']
|
||||||
|
itemFound = False
|
||||||
|
for announceItem in postJsonObject['object']['shares']['items']:
|
||||||
|
if announceItem.get('actor'):
|
||||||
|
if announceItem['actor'] == actor:
|
||||||
|
if debug:
|
||||||
|
print('DEBUG: Announce was removed for ' + actor)
|
||||||
|
anIt = announceItem
|
||||||
|
postJsonObject['object']['shares']['items'].remove(anIt)
|
||||||
|
itemFound = True
|
||||||
|
break
|
||||||
|
if itemFound:
|
||||||
|
if totalItems == 1:
|
||||||
|
if debug:
|
||||||
|
print('DEBUG: shares (announcements) ' +
|
||||||
|
'was removed from post')
|
||||||
|
del postJsonObject['object']['shares']
|
||||||
|
else:
|
||||||
|
itlen = len(postJsonObject['object']['shares']['items'])
|
||||||
|
postJsonObject['object']['shares']['totalItems'] = itlen
|
||||||
|
|
||||||
|
saveJson(postJsonObject, postFilename)
|
||||||
|
|
||||||
|
|
||||||
|
def updateAnnounceCollection(recentPostsCache: {},
|
||||||
|
baseDir: str, postFilename: str,
|
||||||
|
actor: str, domain: str, debug: bool) -> None:
|
||||||
|
"""Updates the announcements collection within a post
|
||||||
|
Confusingly this is known as "shares", but isn't the
|
||||||
|
same as shared items within shares.py
|
||||||
|
It's shares of posts, not shares of physical objects.
|
||||||
|
"""
|
||||||
|
postJsonObject = loadJson(postFilename)
|
||||||
|
if postJsonObject:
|
||||||
|
# remove any cached version of this announce so that the announce
|
||||||
|
# icon is changed
|
||||||
|
nickname = getNicknameFromActor(actor)
|
||||||
|
cachedPostFilename = getCachedPostFilename(baseDir, nickname, domain,
|
||||||
|
postJsonObject)
|
||||||
|
if cachedPostFilename:
|
||||||
|
if os.path.isfile(cachedPostFilename):
|
||||||
|
os.remove(cachedPostFilename)
|
||||||
|
removePostFromCache(postJsonObject, recentPostsCache)
|
||||||
|
|
||||||
|
if not postJsonObject.get('object'):
|
||||||
|
if debug:
|
||||||
|
pprint(postJsonObject)
|
||||||
|
print('DEBUG: post ' + postFilename + ' has no object')
|
||||||
|
return
|
||||||
|
if not isinstance(postJsonObject['object'], dict):
|
||||||
|
return
|
||||||
|
postUrl = postJsonObject['id'].replace('/activity', '') + '/shares'
|
||||||
|
if not postJsonObject['object'].get('shares'):
|
||||||
|
if debug:
|
||||||
|
print('DEBUG: Adding initial shares (announcements) to ' +
|
||||||
|
postUrl)
|
||||||
|
announcementsJson = {
|
||||||
|
"@context": "https://www.w3.org/ns/activitystreams",
|
||||||
|
'id': postUrl,
|
||||||
|
'type': 'Collection',
|
||||||
|
"totalItems": 1,
|
||||||
|
'items': [{
|
||||||
|
'type': 'Announce',
|
||||||
|
'actor': actor
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
postJsonObject['object']['shares'] = announcementsJson
|
||||||
|
else:
|
||||||
|
if postJsonObject['object']['shares'].get('items'):
|
||||||
|
sharesItems = postJsonObject['object']['shares']['items']
|
||||||
|
for announceItem in sharesItems:
|
||||||
|
if announceItem.get('actor'):
|
||||||
|
if announceItem['actor'] == actor:
|
||||||
|
return
|
||||||
|
newAnnounce = {
|
||||||
|
'type': 'Announce',
|
||||||
|
'actor': actor
|
||||||
|
}
|
||||||
|
postJsonObject['object']['shares']['items'].append(newAnnounce)
|
||||||
|
itlen = len(postJsonObject['object']['shares']['items'])
|
||||||
|
postJsonObject['object']['shares']['totalItems'] = itlen
|
||||||
|
else:
|
||||||
|
if debug:
|
||||||
|
print('DEBUG: shares (announcements) section of post ' +
|
||||||
|
'has no items list')
|
||||||
|
|
||||||
|
if debug:
|
||||||
|
print('DEBUG: saving post with shares (announcements) added')
|
||||||
|
pprint(postJsonObject)
|
||||||
|
saveJson(postJsonObject, postFilename)
|
||||||
|
|
Loading…
Reference in New Issue