From b2ecda1b2a930385c4ab545ad5d97b1ee0e39a97 Mon Sep 17 00:00:00 2001
From: Bob Mottram <bob@freedombone.net>
Date: Sun, 14 Jul 2019 10:17:50 +0100
Subject: [PATCH] Proper chronological ordering of posts within boxes

---
 README.md |  1 +
 inbox.py  |  9 ++++++---
 posts.py  | 41 +++++++++++++++++++++++++++++++----------
 3 files changed, 38 insertions(+), 13 deletions(-)

diff --git a/README.md b/README.md
index 31e4eb71..da9cea07 100644
--- a/README.md
+++ b/README.md
@@ -30,6 +30,7 @@ This project is currently *pre alpha* and not recommended for any real world use
  * Data minimization principle. Configurable post expiry time.
  * Likes and repeats only visible to authorized viewers
  * ReplyGuy mitigation: maxmimum replies per post or posts per day
+ * Ability to delete or hide specific conversation threads
  * Commandline interface. If there's a GUI it should be a separate project.
  * Designed for intermittent connectivity. Assume network disruptions.
  * Suitable for single board computers.
diff --git a/inbox.py b/inbox.py
index 4d6f101d..958cb635 100644
--- a/inbox.py
+++ b/inbox.py
@@ -962,15 +962,18 @@ def runInboxQueue(baseDir: str,httpPrefix: str,sendThreads: [],postLog: [], \
 
             # copy any posts addressed to followers into the shared inbox
             # this avoid copying file multiple times to potentially many
-            # individual inboxes            
+            # individual inboxes
+            # TODO This obviously bypasses object capabilities and so
+            # any checking will need to be handled at the time when inbox
+            # GET happens on individual accounts
             if len(recipientsDictFollowers)>0:
                 copyfile(queueFilename, \
                          queueJson['destination'].replace(inboxHandle,inboxHandle))
 
             # for posts addressed to specific accounts
-            for handle,capsId in recipientsDict.items():                    
+            for handle,capsId in recipientsDict.items():              
                 destination=queueJson['destination'].replace(inboxHandle,handle)
-                # check that capabilities are accepted            
+                # check that capabilities are accepted
                 if queueJson['post'].get('capability'):
                     capabilityIdList=queueJson['post']['capability']
                     # does the capability id list within the post contain the id
diff --git a/posts.py b/posts.py
index a9ffac51..c803ccf3 100644
--- a/posts.py
+++ b/posts.py
@@ -17,6 +17,7 @@ import threading
 import sys
 import trace
 import time
+from collections import OrderedDict
 from threads import threadWithTrace
 from cache import storePersonInCache
 from cache import getPersonFromCache
@@ -778,10 +779,19 @@ def createOutbox(baseDir: str,nickname: str,domain: str,port: int,httpPrefix: st
     return createBoxBase(baseDir,'outbox',nickname,domain,port,httpPrefix, \
                          itemsPerPage,headerOnly,authorized,pageNumber)
 
+def getStatusNumberFromPostFilename(filename) -> int:
+    """Gets the status number from a post filename
+    eg. https:##testdomain.com:8085#users#testuser567#statuses#1562958506952068.json
+    returns 156295850695206
+    """
+    if '#statuses#' not in filename:
+        return None
+    return int(filename.split('#')[-1].replace('.json',''))
+
 def createBoxBase(baseDir: str,boxname: str, \
                   nickname: str,domain: str,port: int,httpPrefix: str, \
                   itemsPerPage: int,headerOnly: bool,authorized :bool,pageNumber=None) -> {}:
-    """Constructs the box feed
+    """Constructs the box feed for a person with the given nickname
     """
     if boxname!='inbox' and boxname!='outbox':
         return None
@@ -816,27 +826,38 @@ def createBoxBase(baseDir: str,boxname: str, \
     postsOnPageCtr=0
 
     # post filenames sorted in descending order
-    postsInBox=[]
-    postsInPersonInbox=sorted(os.listdir(boxDir), reverse=True)
+    postsInBoxDict={}
+    postsCtr=0
+    postsInPersonInbox=os.listdir(boxDir)
     for postFilename in postsInPersonInbox:
-        postsInBox.append(os.path.join(boxDir, postFilename))
+        # extract the status number
+        statusNumber=getStatusNumberFromPostFilename(postFilename)
+        if statusNumber:
+            postsInBoxDict[statusNumber]=os.path.join(boxDir, postFilename)
+            postsCtr+=1
 
     # combine the inbox for the account with the shared inbox
     if sharedBoxDir:
-        postsInSharedInbox=sorted(os.listdir(sharedBoxDir), reverse=True)
+        postsInSharedInbox=os.listdir(sharedBoxDir)
         for postFilename in postsInSharedInbox:
-            postsInBox.append(os.path.join(sharedBoxDir, postFilename))
+            statusNumber=getStatusNumberFromPostFilename(postFilename)
+            if statusNumber:
+                postsInBoxDict[statusNumber]=os.path.join(sharedBoxDir, postFilename)
+                postsCtr+=1
+
+    # sort the list in descending order of date
+    postsInBox=OrderedDict(sorted(postsInBoxDict.items(),reverse=True))
 
     # number of posts in box
-    boxHeader['totalItems']=len(postsInBox)
+    boxHeader['totalItems']=postsCtr
     prevPostFilename=None
 
     if not pageNumber:
         pageNumber=1
 
     # Generate first and last entries within header
-    if len(postsInBox)>0:
-        lastPage=int(len(postsInBox)/itemsPerPage)
+    if postsCtr>0:
+        lastPage=int(postsCtr/itemsPerPage)
         if lastPage<1:
             lastPage=1
         boxHeader['last']= \
@@ -845,7 +866,7 @@ def createBoxBase(baseDir: str,boxname: str, \
     # Insert posts
     currPage=1
     postsCtr=0
-    for postFilename in postsInBox:
+    for statusNumber,postFilename in postsInBox.items():
         # Are we at the starting page yet?
         if prevPostFilename and currPage==pageNumber and postsCtr==0:
             # update the prev entry for the last message id