$ git clone https://socialnetwork.ion.nu/socialnetwork-web.git
commit fa5239f69c4fa8b09ebde20b42366d9f5ab21dbb
Author: Alicia <...>
Date:   Sat Jul 22 12:50:46 2017 +0200

    Implemented privacy getting/setting.

diff --git a/Makefile b/Makefile
index 6677b19..9d07c5d 100644
--- a/Makefile
+++ b/Makefile
@@ -8,7 +8,7 @@ SOCIALNETWORKREVISION=f5e92db17a617d4777df4b7a1107d67114d3b6f9
 REVISION=$(shell git log | sed -n -e 's/^commit //p;q')
 JSLIBS=$(shell PKG_CONFIG_PATH=toolchain/usr/lib/pkgconfig pkg-config --libs gnutls nettle hogweed) -lgmp
 JSCFLAGS=$(shell PKG_CONFIG_PATH=toolchain/usr/lib/pkgconfig pkg-config --cflags gnutls)
-JSSYMBOLS='_social_init','_peer_handlesocket','_peer_new_unique','_websockproxy_read','_websockproxy_setwrite','_getcirclecount','_newcircle','_circle_getcount','_circle_getname','_circle_setname','_social_addfriend','_circle_getid','_social_finduser','_self_getid','_user_getupdatecount','_user_getupdatetype','_user_getupdatetimestamp'
+JSSYMBOLS='_social_init','_peer_handlesocket','_peer_new_unique','_websockproxy_read','_websockproxy_setwrite','_getcirclecount','_circle_getcount','_circle_getname','_circle_getprivacyptr','_social_addfriend','_circle_getid','_social_finduser','_self_getid','_user_getupdatecount','_user_getupdatetype','_user_getupdatetimestamp','_setcircle','_privacy_getflags','_privacy_getcirclecount','_privacy_getcircle'
 
 all: webpeer libsocial.js
 
diff --git a/jsglue.c b/jsglue.c
index bf0b858..e140e43 100644
--- a/jsglue.c
+++ b/jsglue.c
@@ -50,27 +50,6 @@ unsigned int getcirclecount(void)
 {
   return social_self->circlecount;
 }
-
-// Find an empty circle and reset it, empty and nameless I guess, then return its index
-unsigned int newcircle(void)
-{
-  unsigned int i;
-  for(i=0; i<social_self->circlecount; ++i)
-  {
-    if(!social_self->circles[i].count && !social_self->circles[i].name){break;}
-  }
-  if(i==social_self->circlecount)
-  {
-    ++social_self->circlecount;
-    social_self->circles=realloc(social_self->circles, social_self->circlecount*sizeof(struct friendslist));
-  }else{
-    free(social_self->circles[i].privacy.circles);
-  }
-  memset(&social_self->circles[i], 0, sizeof(struct friendslist));
-  return i;
-}
-// TODO: how to configure privacy structs?
-
 unsigned int circle_getcount(unsigned int i)
 {
   if(i>=social_self->circlecount){return 0;}
@@ -90,12 +69,16 @@ const char* circle_getname(unsigned int i)
   if(i>=social_self->circlecount){return 0;}
   return social_self->circles[i].name;
 }
-void circle_setname(unsigned int i, const char* name)
+void setcircle(uint32_t circle, const char* name, uint8_t flags, void* circles, uint32_t circlecount)
 {
-  if(i>=social_self->circlecount){return;}
-  free(social_self->circles[i].name);
-  social_self->circles[i].name=strdup(name);
+  struct privacy priv={
+    .flags=flags,
+    .circles=circles,
+    .circlecount=circlecount
+  };
+  social_setcircle(circle, name, &priv);
 }
+struct privacy* circle_getprivacyptr(uint32_t circle){return &social_self->circles[circle].privacy;}
 unsigned int user_getupdatecount(struct user* user){return user->updatecount;}
 const char* user_getupdatetype(struct user* user, unsigned int index)
 {
@@ -120,3 +103,6 @@ const char* self_getid(void)
   sprintf(id, PEERFMT, PEERARG(social_self->id));
   return id;
 }
+uint8_t privacy_getflags(struct privacy* priv){return priv->flags;}
+uint32_t privacy_getcirclecount(struct privacy* priv){return priv->circlecount;}
+uint32_t privacy_getcircle(struct privacy* priv, uint32_t i){return priv->circles[i];}
diff --git a/libsocialjs.js b/libsocialjs.js
index 2f65c8c..097d444 100644
--- a/libsocialjs.js
+++ b/libsocialjs.js
@@ -26,17 +26,20 @@ function websockwrite(addr, addrlen, buf, size)
 var websockproxy_read;
 var peer_new_unique;
 var getcirclecount;
-var newcircle;
 var circle_getcount;
 var circle_getname;
-var circle_setname;
 var circle_getid;
+var setcircle;
+var circle_getprivacyptr;
 var social_addfriend;
 var social_finduser;
 var user_getupdatecount;
 var user_getupdatetype;
 var user_getupdatetimestamp;
 var self_getid;
+var privacy_getflags;
+var privacy_getcirclecount;
+var privacy_getcircle;
 
 var websockproxy_to=false;
 var firstpacket=true;
@@ -73,17 +76,20 @@ function init(privkey)
   peer_new_unique=Module.cwrap('peer_new_unique', 'array', ['number', 'array', 'number']);
   // Low level access functions
   getcirclecount=Module.cwrap('getcirclecount', 'number', []);
-  newcircle=Module.cwrap('newcircle', 'number', []);
   circle_getcount=Module.cwrap('circle_getcount', 'number', ['number']);
   circle_getname=Module.cwrap('circle_getname', 'string', ['number']);
-  circle_setname=Module.cwrap('circle_setname', null, ['number', 'string']);
   circle_getid=Module.cwrap('circle_getid', 'string', ['number','number']);
+  setcircle=Module.cwrap('setcircle', null, ['number','string','number','array','number']);
+  circle_getprivacyptr=Module.cwrap('circle_getprivacyptr', 'number', ['number']);
   social_addfriend=Module.cwrap('social_addfriend', null, ['array', 'number']);
   social_finduser=Module.cwrap('social_finduser', 'number', ['array']);
   user_getupdatecount=Module.cwrap('user_getupdatecount', 'number', ['number']);
   user_getupdatetype=Module.cwrap('user_getupdatetype', 'string', ['number','number']);
   user_getupdatetimestamp=Module.cwrap('user_getupdatetimestamp', 'number', ['number','number']);
   self_getid=Module.cwrap('self_getid', 'string', []);
+  privacy_getflags=Module.cwrap('privacy_getflags', 'number', ['number']);
+  privacy_getcirclecount=Module.cwrap('privacy_getcirclecount', 'number', ['number']);
+  privacy_getcircle=Module.cwrap('privacy_getcircle', 'number', ['number', 'number']);
 
   _websockproxy_setwrite(Runtime.addFunction(websockwrite));
   FS.writeFile('privkey.pem', privkey, {});
@@ -116,6 +122,34 @@ function circle_getfriends(index)
   }
   return friends;
 }
+function newcircle()
+{
+  var len=getcirclecount();
+  for(var i=0; i<len; ++i)
+  {
+    var name=circle_getname(i);
+    var count=circle_getcount(i);
+    if((!name || name=='') && count==0){break;}
+  }
+  return i;
+}
+function circle_getprivacy(index)
+{
+  var ptr=circle_getprivacyptr(index);
+  return getprivacy(ptr);
+}
+function circle_set(index, name, priv)
+{
+  var circles=[];
+  for(circle of priv.circles) // Can only pass Uint8 arrays, so construct our Uint32 values from Uint8 pieces
+  {
+    circles.push(circle.index&0xff);
+    circles.push((circle.index&0xff00)/0x100);
+    circles.push((circle.index&0xff0000)/0x10000);
+    circles.push((circle.index&0xff000000)/0x1000000);
+  }
+  setcircle(configcircle_index, name, priv.flags, new Uint8Array(circles), priv.circles.length);
+}
 function hextobin(hex)
 {
   var bin=new Array();
@@ -144,3 +178,24 @@ function user_getupdate(user, index)
 // TODO: Get type-specific data
   return update;
 }
+function privacy(flags, circles)
+{
+  this.flags=flags;
+  this.circles=circles;
+}
+function getprivacy(ptr)
+{
+  var flags=privacy_getflags(ptr);
+  var count=privacy_getcirclecount(ptr);
+  var circles=[];
+  for(var i=0; i<count; ++i)
+  {
+    var index=privacy_getcircle(ptr, i);
+    var circle={
+      'name':circle_getname(index),
+      'index':index
+    };
+    circles.push(circle);
+  }
+  return new privacy(flags, circles);
+}
diff --git a/websocial.html b/websocial.html
index 26a9885..b205bca 100644
--- a/websocial.html
+++ b/websocial.html
@@ -46,11 +46,11 @@
   <div class="modal" id="circle_window" style="display:none;">
     Name:<input type="text" id="circle_name" /><br />
     Privacy:<select id="circle_privacy">
-      <option>Select circles only</option>
-      <option>Friends</option>
-      <option>Everyone</option>
+      <option value="0">Select circles only</option>
+      <option value="2">Friends</option>
+      <option value="1">Everyone</option>
     </select><br />
-    <div id="circle_circles"></div><br />
+    <div id="circle_privacy_circles"></div><br />
     <button onclick="circle_save();">OK</button> &nbsp; <button onclick="chdisplay('circle_window',false);">Cancel</button>
   </div>
   <!-- Main interface -->
diff --git a/websocial.js b/websocial.js
index 78e6890..e6e4c0c 100644
--- a/websocial.js
+++ b/websocial.js
@@ -103,8 +103,7 @@ function page_friends()
   {
     if(this.value=='new')
     {
-      configcircle_option=this.selectedOptions[0];
-      circle_openconfig(newcircle());
+      circle_openconfig(newcircle(), this.selectedOptions[0]);
     }
   }
   box.appendChild(circle);
@@ -114,7 +113,6 @@ function page_friends()
   {
     if(circle.value=='new') // Need to create the circle first
     {
-      configcircle_option=circle.selectedOptions[0]; // option;
       circle.onchange();
       return;
     }
@@ -127,7 +125,12 @@ function page_friends()
   // TODO: Make this prettier, links to individual profile pages and get the name property
   for(item of circles)
   {
-    display.appendChild(document.createTextNode('Circle '+item.name+':'));
+    display.appendChild(document.createTextNode('Circle '+item.name+': '));
+    var button=document.createElement('button');
+    button.appendChild(document.createTextNode('Options'));
+    button.dataset.index=item.index;
+    button.onclick=function(){circle_openconfig(this.dataset.index, false);};
+    display.appendChild(button);
     display.appendChild(document.createElement('br'));
     var count=circle_getcount(item.index);
     for(var i=0; i<count; ++i)
@@ -161,15 +164,70 @@ function page_user(id)
 // TODO: Option to load more updates
 }
 
+function privacy_openconfig(prefix, priv)
+{
+  var circlebox=document.getElementById(prefix+'_privacy_circles');
+  dom_clear(circlebox);
+  var listed=[];
+  function listcircle(item, check)
+  {
+    if(listed.indexOf(item.index)>-1){return;}
+    listed.push(item.index);
+    var label=document.createElement('label');
+    if(item.title){label.title=item.title;}
+    var checkbox=document.createElement('input');
+    if(check){checkbox.checked=true;}
+    checkbox.type='checkbox';
+    checkbox.name=prefix+'_privacy_circles';
+    checkbox.value=item.index;
+    label.appendChild(checkbox);
+    label.appendChild(document.createTextNode(item.name+' '));
+    circlebox.appendChild(label);
+  }
+  if(priv) // Populate with circles from existing privacy
+  {
+    document.getElementById(prefix+'_privacy').value=priv.flags; // And the flags
+    for(item of priv.circles)
+    {
+      listcircle(item, true);
+    }
+  }
+  // Populate with circles from libsocial
+  var circles=getcircles();
+  for(item of circles)
+  {
+    listcircle(item, false);
+  }
+}
+
+function privacy_save(prefix)
+{
+  var circles=[];
+  var flags=document.getElementById(prefix+'_privacy').value;
+  var items=document.getElementsByName(prefix+'_privacy_circles');
+  for(item of items)
+  {
+    if(item.checked){circles.push({'index':item.value});}
+  }
+  return new privacy(flags, circles);
+}
+
 var configcircle_index;
 var configcircle_option=false;
-function circle_openconfig(index)
+function circle_openconfig(index, option)
 {
-  // TODO: Move circle-list population to its own function? likely needed for other privacy settings
-  var circles=document.getElementById('circle_circles');
-  dom_clear(circles);
-  // TODO: Populate with circles from libsocial
-  // TODO: Store index and checkbox elements for circle_save()
+  configcircle_option=option;
+  if(option && option.value=='new')
+  {
+    var priv={'flags':0,'circles':[{
+      'index':index,
+      'name':'This circle',
+      'title':'Let friends in this circle know about your other friends in the circle'
+    }]};
+  }else{
+    var priv=circle_getprivacy(index);
+  }
+  privacy_openconfig('circle', priv);
   var name=document.getElementById('circle_name');
   name.value=circle_getname(index);
   configcircle_index=index;
@@ -179,7 +237,8 @@ function circle_openconfig(index)
 function circle_save()
 {
   var name=document.getElementById('circle_name');
-  circle_setname(configcircle_index, name.value);
+  var priv=privacy_save('circle');
+  circle_set(configcircle_index, name.value, priv);
   if(configcircle_option)
   {
     var select=configcircle_option.parentNode;