$ git clone https://socialnetwork.ion.nu/socialnetwork.git
commit a23bf0c34e06d2021219c01ede044f3e16c20b4f
Author: Alicia <...>
Date:   Mon Jul 31 18:13:43 2017 +0200

    Rotate update files and keep some updates (e.g. fields, friend updates) in a separate file that never rotates.

diff --git a/Makefile b/Makefile
index 7ebe605..bf51663 100644
--- a/Makefile
+++ b/Makefile
@@ -1,5 +1,5 @@
 LIBS=$(shell pkg-config --libs gnutls)
-CFLAGS=-g3 -Wall $(shell pkg-config --cflags gnutls)
+CFLAGS=-g3 -Wall -Wextra $(shell pkg-config --cflags gnutls)
 PREFIX=/usr
 
 all: socialtest libsocial.so libsocial.pc
diff --git a/social.c b/social.c
index 6c05f24..b68b316 100644
--- a/social.c
+++ b/social.c
@@ -64,9 +64,24 @@ static void user_save(struct user* user)
   close(f);
 }
 
+static void user_loadfrom(struct user* user, const char* suffix)
+{
+  char path[strlen(social_prefix)+strlen("/updates/0")+ID_SIZE*2+strlen(suffix)];
+  sprintf(path, "%s/updates/"PEERFMT"%s", social_prefix, PEERARG(user->id), suffix);
+  int f=open(path, O_RDONLY);
+  if(f<0){return;}
+  uint64_t size;
+  while(read(f, &size, sizeof(size))==sizeof(size))
+  {
+    uint8_t buf[size];
+    read(f, buf, size);
+    social_update_parse(user, buf, size);
+  }
+}
+
 static void user_load(struct user* user)
 {
-  // Load user data (only pubkey atm), but spare pubkey if it's already set
+  // Load public key if it isn't already set
   if(!user->pubkey)
   {
     char path[strlen(social_prefix)+strlen("/users/0")+ID_SIZE*2];
@@ -84,18 +99,18 @@ static void user_load(struct user* user)
       gnutls_pubkey_import(user->pubkey, &key, GNUTLS_X509_FMT_PEM);
     }
   }
-  // Load updates
-  char path[strlen(social_prefix)+strlen("/updates/0")+ID_SIZE*2];
-  sprintf(path, "%s/updates/"PEERFMT, social_prefix, PEERARG(user->id));
-  int f=open(path, O_RDONLY);
-  if(f<0){return;}
-  uint64_t size;
-  while(read(f, &size, sizeof(size))==sizeof(size))
+  // Load updates (sticky and unrotated)
+  user_loadfrom(user, ".sticky");
+  user_loadfrom(user, "");
+  // Count the number of rotated updates we have for the user (updates which we don't load here but can be loaded with social_user_loadmore() on demand)
+  unsigned int i;
+  for(i=0; i<UINT_MAX; ++i)
   {
-    uint8_t buf[size];
-    read(f, buf, size);
-    social_update_parse(user, buf, size);
+    char path[snprintf(0,0, "%s/updates/"PEERFMT".%u", social_prefix, PEERARG(user->id), i+1)+1];
+    sprintf(path, "%s/updates/"PEERFMT".%u", social_prefix, PEERARG(user->id), i+1);
+    if(access(path, F_OK)){break;}
   }
+  user->rotationcount=i;
 }
 
 static struct user* user_new(const unsigned char id[ID_SIZE])
@@ -104,12 +119,13 @@ static struct user* user_new(const unsigned char id[ID_SIZE])
   memcpy(user->id, id, ID_SIZE);
   user->pubkey=0;
   user->peer=peer_findbyid(id);
-  user->name=0;
   user->circles=0;
   user->circlecount=0;
   user->seq=0;
   user->updates=0;
   user->updatecount=0;
+  user->rotation=0;
+  user->rotationcount=0;
   ++social_usercount;
   social_users=realloc(social_users, sizeof(void*)*social_usercount);
   social_users[social_usercount-1]=user;
@@ -328,6 +344,17 @@ void social_user_removefromcircle(struct user* user, uint32_t circle, const unsi
   }
 }
 
+unsigned int social_user_loadmore(struct user* user)
+{
+  if(user->rotation==user->rotationcount){return 0;} // Already loaded all updates
+  unsigned int oldcount=user->updatecount;
+  ++user->rotation;
+  char buf[snprintf(0,0,".%u", user->rotation)+1];
+  sprintf(buf, ".%u", user->rotation);
+  user_loadfrom(user, buf);
+  return user->updatecount-oldcount;
+}
+
 void social_addfriend(const unsigned char id[ID_SIZE], uint32_t circle)
 {
   struct user* friend=social_finduser(id);
diff --git a/social.h b/social.h
index 1a181a3..087a156 100644
--- a/social.h
+++ b/social.h
@@ -49,17 +49,33 @@ struct friendslist
   unsigned int count;
 };
 
+/**
+* user:
+* @id: Peer ID
+* @pubkey: Public key
+* @peer: Peer structure, or NULL if they are not connected
+* @circles: Friend circles
+* @circlecount: Number of friend circles
+* @seq: Sequence number of the last received (and confirmed) update
+* @updates: Updates
+* @updatecount: Number of updates
+* @rotation: Current number of rotating update files loaded
+* @rotationcount: Total number of rotating update files for this user
+*
+* User structure, keeps track of updates, public keys, peer if connected, etc.
+*/
 struct user
 {
   unsigned char id[ID_SIZE];
   gnutls_pubkey_t pubkey;
   struct peer* peer;
-  const char* name;
   struct friendslist* circles;
   unsigned int circlecount;
   uint64_t seq; // Sequence of updates we have from this user. 64 bits should be enough for a lifetime of updates (18446744073709551616 updates, enough to update 584 times per millisecond for a million years)
   struct update* updates;
   unsigned int updatecount;
+  unsigned int rotation;
+  unsigned int rotationcount;
 };
 
 extern struct user** social_users;
@@ -77,6 +93,14 @@ extern void social_init(const char* keypath, const char* pathprefix);
 extern struct friendslist* social_user_getcircle(struct user* user, uint32_t circle);
 extern void social_user_addtocircle(struct user* user, uint32_t circle, const unsigned char id[ID_SIZE]);
 extern void social_user_removefromcircle(struct user* user, uint32_t circle, const unsigned char id[ID_SIZE]);
+/**
+* social_user_loadmore:
+* @user: User to load more updates for
+*
+* Load more updates for a user from the filesystem
+* Returns: The number of additional updates loaded (will be 0 when there is nothing more to load)
+*/
+extern unsigned int social_user_loadmore(struct user* user);
 extern void social_addfriend(const unsigned char id[ID_SIZE], uint32_t circle);
 extern void social_removefriend(const unsigned char id[ID_SIZE], uint32_t circle);
 /**
diff --git a/socialtest.c b/socialtest.c
index be77a70..d6cc161 100644
--- a/socialtest.c
+++ b/socialtest.c
@@ -139,6 +139,13 @@ int main(int argc, char** argv)
           }
         }
       }
+      else if(!strcmp(buf, "loadmore") || // Load more of our own updates
+              !strncmp(buf, "loadmore ", 9)) // Load more of someone else's updates
+      {
+        struct user* user=(buf[8]?finduser(&buf[9]):social_self);
+        if(!user){printf("User not found\n"); continue;}
+        social_user_loadmore(user);
+      }
       else if(!strncmp(buf, "addfriend ", 10))
       {
         if(strlen(&buf[10])<ID_SIZE*2){continue;}
diff --git a/update.c b/update.c
index aa69d92..3d1e8f1 100644
--- a/update.c
+++ b/update.c
@@ -109,8 +109,11 @@ void social_update_sign(struct update* update)
 
 void social_update_save(struct user* user, struct update* update)
 {
-  char path[strlen(social_prefix)+strlen("/updates/0")+ID_SIZE*2];
-  sprintf(path, "%s/updates/"PEERFMT, social_prefix, PEERARG(user->id));
+  // Based on update type consider some updates "sticky", e.g. fields, friends, media (just the metadata, hash+name+size)
+  // and save them to an alternative, non-rotated updates file
+  char sticky=(update->type==UPDATE_FIELD || update->type==UPDATE_MEDIA || update->type==UPDATE_FRIENDS);
+  char path[strlen(social_prefix)+strlen("/updates/.sticky0")+ID_SIZE*2];
+  sprintf(path, "%s/updates/"PEERFMT"%s", social_prefix, PEERARG(user->id), sticky?".sticky":"");
   mkdirp(path);
   int f=open(path, O_WRONLY|O_CREAT|O_APPEND, 0600);
   struct buffer buf;
@@ -122,6 +125,12 @@ void social_update_save(struct user* user, struct update* update)
   write(f, update->signature, update->signaturesize);
   write(f, buf.buf, buf.size);
   buffer_deinit(buf);
+  // Check position of f and rotate if it's large (100kb), and non-sticky
+  // TODO: Make the 100kb limit configurable
+  if(!sticky && lseek(f, 0, SEEK_CUR)>100*1024)
+  {
+    social_update_rotate(user);
+  }
   close(f);
 // TODO: Is it bad to close and reopen a file in rapid succession? if it is maybe we should implement some kind of cache for cases where we're saving many updates fast, like receiving someone else's updates for the first time
 }
@@ -320,3 +329,19 @@ struct update* social_update_parse(struct user* user, void* data, unsigned int l
   privcpy(update->privacy, privacy);
   return update;
 }
+
+void social_update_rotate(struct user* user)
+{
+  unsigned int i;
+  for(i=user->rotationcount+1; i>0; --i)
+  {
+    #define FMT "%s/updates/"PEERFMT".%u"
+    char oldname[snprintf(0,0, FMT, social_prefix, PEERARG(user->id), i-1)+1];
+    sprintf(oldname, (i>1)?FMT:"%s/updates/"PEERFMT, social_prefix, PEERARG(user->id), i-1);
+    char newname[snprintf(0,0, FMT, social_prefix, PEERARG(user->id), i)+1];
+    sprintf(newname, FMT, social_prefix, PEERARG(user->id), i);
+    if(rename(oldname, newname)){perror("rename(social_update_rotate)");}
+  }
+  ++user->rotationcount;
+  ++user->rotation; // By rotating updates we're also shifting which number we're currently on
+}
diff --git a/update.h b/update.h
index 0504d39..dac291d 100644
--- a/update.h
+++ b/update.h
@@ -81,3 +81,10 @@ extern struct update* social_update_getfield(struct user* user, const char* name
 extern struct update* social_update_getfriend(struct user* user, uint32_t circle, const unsigned char id[ID_SIZE]);
 extern struct update* social_update_getcircle(struct user* user, uint32_t circle);
 extern struct update* social_update_parse(struct user* user, void* data, unsigned int len); // Both for receiving updates and loading them from file
+/**
+* social_update_rotate:
+* @user: User to rotate updates for
+*
+* Rotate update files so that we can load older updates only when we want them
+*/
+extern void social_update_rotate(struct user* user);