OK, patch for the *very* work in progress setannotation/getannotation
(also includes the merged patch for the sql table re-use issue)
There are some small changes to EString (new operator to allow adding a
number onto a string directly)
Command::mailboxName is overloaded to allow passing an EString and
return the mailbox (rather than just being able to parse). The parse
method parses the string then passes to the new method.
I needed these at various points in testing - they may not be something
you want to keep. (And this version of the patch might not even use them.)
Apart from that it is the annotation code which is (1) quick & dirty,
(2) has huge amounts of logging - way more than you would ever want, (3)
in the proof of concept category and not for serious merging yet as it
does not really implement the required RFC other than the minimum to
work with SOGo in my specific tests [so may not work for others,
depending on their circumstances].
Jim
Only in aox-jim/aox: buildinfo.inc
Only in aox-jim/: AOX
Only in aox-jim/: bin
Only in aox-jim/: build
diff -rwu aox-master/core/estring.h aox-jim/core/estring.h
--- aox-master/core/estring.h 2014-06-03 13:15:21.000000000 +0000
+++ aox-jim/core/estring.h 2014-07-06 20:26:10.016613000 +0000
@@ -40,6 +40,7 @@
EString & operator=( const EString & );
EString & operator=( const char * );
EString & operator+=( const EString & str ) { append( str ); return *this;
}
+ EString & operator+=( const int64 & num ) { appendNumber(num); return
*this; }
void operator delete( void * );
Only in aox-jim/db: downgrades.inc
Only in aox-jim/db: privileges.inc
Only in aox-jim/doc: man
diff -rwu aox-master/imap/command.cpp aox-jim/imap/command.cpp
--- aox-master/imap/command.cpp 2014-06-03 13:15:21.000000000 +0000
+++ aox-jim/imap/command.cpp 2014-07-06 20:26:11.428613000 +0000
@@ -51,6 +51,7 @@
#include "handlers/thread.h"
#include "handlers/unselect.h"
#include "handlers/urlfetch.h"
+#include "handlers/folderannotation.h"
#include <sys/time.h> // gettimeofday, struct timeval
@@ -262,6 +263,10 @@
c = new GetQuotaRoot();
else if ( n == "setquotaroot" )
c = new SetQuotaRoot();
+ else if ( n == "getannotation" )
+ c = new GetAnnotation;
+ else if ( n == "setannotation" )
+ c = new SetAnnotation;
if ( c ) {
authenticated = true;
@@ -1223,6 +1228,11 @@
UString Command::mailboxName()
{
EString n = astring();
+ return mailboxName(n);
+}
+
+UString Command::mailboxName(const EString &source) {
+ EString n = source;
if ( n.endsWith( "/" ) )
n = n.mid( 0, n.length() - 1 );
diff -rwu aox-master/imap/command.h aox-jim/imap/command.h
--- aox-master/imap/command.h 2014-06-03 13:15:21.000000000 +0000
+++ aox-jim/imap/command.h 2014-07-06 20:26:11.436613000 +0000
@@ -82,6 +82,7 @@
uint msn();
EString flag();
class Mailbox * mailbox();
+ UString mailboxName(const EString &);
UString mailboxName();
void end();
diff -wru --new-file aox-master/imap/handlers/folderannotation.cpp
aox-jim/imap/handlers/folderannotation.cpp
--- aox-master/imap/handlers/folderannotation.cpp 1970-01-01
00:00:00.000000000 +0000
+++ aox-jim/imap/handlers/folderannotation.cpp 2014-07-07 12:39:47.156613000
+0000
@@ -0,0 +1,538 @@
+// Copyright 2009 The Archiveopteryx Developers <[email protected]>
+
+#include <exception>
+
+#include "folderannotation.h"
+
+#include "imap.h"
+#include "user.h"
+#include "query.h"
+#include "mailbox.h"
+#include "transaction.h"
+#include "imapparser.h"
+
+
+/*! \class SetAnnotation annotation.h
+ Adds a mailbox to the annotation list (RFC TBC)
+*/
+
+SetAnnotation::SetAnnotation()
+ : q( 0 ), m( 0 ), trans (0)
+{
+ queryPhase = 0;
+ nameID = 0;
+ annotID = 0;
+}
+
+
+/*! \class GetAnnotation annotation.h
+ Retrieves a mailbox from the annotation list (RFC TBC)
+*/
+
+GetAnnotation::GetAnnotation()
+ : q( 0 ), m( 0 )
+{
+ queryPhase = 0;
+}
+
+
+void SetAnnotation::parse()
+{
+ space();
+ m = mailbox();
+ log( "ANNOT setannotation I think I'm looking at mailbox " +
m->name().ascii() );
+ space();
+ annotationName = astring();
+ log( "ANNOT setannotation I think I'm looking at " + annotationName );
+ space();
+ require( "(" );
+ annotationContext = astring();
+ log( "ANNOT setannotation I think I'm looking at context " +
annotationContext );
+ space();
+ annotationValue = astring();
+ log( "ANNOT setannotation I think I'm looking at value " +
annotationValue );
+ require( ")" );
+ end();
+ if (!ok())
+ return;
+}
+
+
+void SetAnnotation::execute()
+{
+ if ( state() != Executing )
+ return;
+
+ if ( m->deleted() )
+ error( No, "Cannot setannotation on a deleted mailbox" );
+
+ requireRight( m, Permissions::Lookup );
+
+ if ( !ok() || !permitted() )
+ return;
+
+ if (!trans) {
+ log( "ANNOT OK to setannotation " + m->name().ascii() );
+ trans = new Transaction(this);
+ log( "ANNOT transaction created " + m->name().ascii() );
+ }
+
+ if (0 == queryPhase) {
+ log( "ANNOT query phase 0 " + m->name().ascii() );
+ // Now we need to check for the correct annotation name ID and
then store the
+ // value as the private or public value
+ // value.priv is the private context and value.shared is the
public (shared) context
+ //
+ q = new Query( "select id from mailbox_annotation_names where
name = $1", this );
+ q->bind(1, annotationName );
+
+ queryPhase = 1;
+
+ log( "ANNOT query 1 created " + m->name().ascii() );
+ trans->enqueue(q);
+ trans->execute();
+ log( "ANNOT query 1 enqueue/execute " + m->name().ascii() );
+ }
+
+ log( "ANNOT query phase check for 1 " + m->name().ascii() );
+ if (1 == queryPhase) {
+ log( "ANNOT query phase 1 " + m->name().ascii() );
+ if (q->failed()) {
+ log( "ANNOT failed query" + q->error());
+ trans->rollback();
+ finish();
+ return;
+ }
+ if (q->done()) {
+ log( "ANNOT query 1 done " + m->name().ascii() );
+ queryPhase = 2;
+ } else {
+ log( "ANNOT query 1 NOT done " + m->name().ascii() );
+ return;
+ }
+ }
+ log( "ANNOT query phase check for 2 " + m->name().ascii() );
+ if (2 == queryPhase) {
+ if (!(q->hasResults())) {
+ log( "ANNOT query 1 NO RESULTS " + m->name().ascii() );
+ // OK, 0 Rows, so this must be a new context we need to
add to the table
+ delete q; // GC no longer required query
+ q = new Query ("insert into mailbox_annotation_names
(name) values ($1) returning id", this);
+ q->bind(1, annotationName );
+
+ queryPhase = 3;
+
+ trans->enqueue(q);
+ trans->execute();
+ // We should always have rows unless we've had some
crazy contention with someone else
+ // doing this at exactly the same time.
+ // for now we say that is unlikely but we should
probably do some sort of checking
+ } else {
+ log( "ANNOT query 1 has results " + m->name().ascii() );
+ queryPhase = 4;
+ }
+ }
+
+ log( "ANNOT query phase check for 3 " + m->name().ascii() );
+ if (3 == queryPhase) {
+ log( "ANNOT query phase 3 " + m->name().ascii() );
+ if (q->failed()) {
+ log( "ANNOT failed query" + q->error());
+ trans->rollback();
+ finish();
+ return;
+ }
+ if (q->done()) {
+ log( "ANNOT query 2 done " + m->name().ascii() );
+ queryPhase = 4;
+ } else {
+ log( "ANNOT query 2 NOT done " + m->name().ascii() );
+ return;
+ }
+ }
+
+ if (4 == queryPhase) {
+ // OK, we're assuming one or other of the previous queries
produced 1 row with our id
+ Row *r = q->nextRow();
+ nameID = r->getInt("id");
+
+ log( "ANNOT OK to setannotation for id " + nameID );
+ log( "ANNOT OK to setannotation for mailbox id " + m->id() );
+ log( "ANNOT OK to setannotation for owner " +
imap()->user()->id() );
+ log( "ANNOT OK to setannotation for " + annotationContext);
+ log( "ANNOT OK to setannotation for value " + annotationValue );
+
+
+ delete q;
+ q = new Query( "select id from mailbox_annotations where
mailbox=$1 and name=$2 for update", this );
+
+ q->bind(1, m->id());
+ q->bind(2, nameID);
+
+ queryPhase = 5;
+
+ trans->enqueue(q);
+ trans->execute();
+ }
+
+
+ log( "ANNOT query phase check for 5 " + m->name().ascii() );
+ if (5 == queryPhase) {
+ log( "ANNOT query phase 5 " + m->name().ascii() );
+ if (q->failed()) {
+ log( "ANNOT failed query" + q->error());
+ trans->rollback();
+ finish();
+ return;
+ }
+ if (q->done()) {
+ log( "ANNOT query 3 done " + m->name().ascii() );
+ queryPhase = 6;
+ } else {
+ log( "ANNOT query 3 NOT done " + m->name().ascii() );
+ return;
+ }
+ }
+
+ log( "ANNOT query phase check for 6 " + m->name().ascii() );
+ if (6 == queryPhase) {
+ EString sql;
+ if (!(q->hasResults())) {
+ log( "ANNOT query 4 NO RESULTS " + m->name().ascii() );
+ // Now, we need to put this into the annotations table
+ // owner stuff skipped right now
+ sql = "insert into mailbox_annotations (mailbox, owner,
name, ";
+ if (annotationContext == "value.priv")
+ sql.append("privatevalue");
+ else
+ sql.append("sharedvalue");
+
+ sql.append(") values ($1, $2, $3, $4)");
+
+ delete q; // GC old query
+ q = new Query (sql, this);
+ q->bind(1, m->id());
+ q->bind(2, imap()->user()->id());
+ q->bind(3, nameID);
+ q->bind(4, annotationValue);
+ } else {
+ Row *r = q->nextRow();
+ annotID = r->getInt("id");
+
+ log( "ANNOT OK to setannotation for id " + annotID );
+ sql = "update mailbox_annotations set ";
+ if (annotationContext == "value.priv")
+ sql.append("privatevalue=$1");
+ else
+ sql.append("sharedvalue=$1");
+
+ sql.append(" where id=$2");
+
+ delete q; // GC old query
+ q = new Query (sql, this);
+ q->bind(1, annotationValue);
+ q->bind(2, annotID);
+ }
+
+ queryPhase = 7;
+
+ trans->enqueue(q);
+ trans->execute();
+ }
+
+// Wait for query 7 ???
+
+ if (7 == queryPhase) {
+ trans->commit();
+ }
+
+ finish();
+}
+
+
+void GetAnnotation::parse()
+{
+ EString requiredMailbox;
+ EString requiredName;
+ EString requiredValue;
+
+ space();
+
+ // mbox-or-pat = list-mailbox / patterns
+ // patterns = "(" list-mailbox *(SP list-mailbox) ")"
+ if ( present( "(" ) ) {
+ mailboxesRequested.append( parser()->listMailbox() );
+
+ while ( present( " " ) ) {
+ mailboxesRequested.append( parser()->listMailbox() );
+ }
+ require( ")" );
+ } else {
+ mailboxesRequested.append( parser()->listMailbox() );
+ }
+
+ space();
+
+ // Annotation name(s) requested
+ if ( present( "(" ) ) {
+ annotationsRequested.append( parser()->listMailbox() );
+ while ( present( " " ) )
+ annotationsRequested.append( parser()->listMailbox() );
+ require( ")" );
+ } else {
+ annotationsRequested.append( parser()->listMailbox() );
+ }
+
+ space();
+
+ // Value(s) requested
+ if ( present( "(" ) ) {
+ valuesRequested.append( parser()->listMailbox() );
+ while ( present( " " ) )
+ valuesRequested.append( parser()->listMailbox() );
+ require( ")" );
+ } else {
+ valuesRequested.append( parser()->listMailbox() );
+ }
+
+
+ end();
+
+ if ( ok() )
+ log( "ANNOT getannotation OK");
+ else
+ log( "ANNOT getannotation BAD");
+}
+
+
+void GetAnnotation::execute()
+{
+ if ( state() != Executing )
+ return;
+
+// if ( m->deleted() )
+// error( No, "Cannot getannotation on a deleted mailbox" );
+
+// requireRight( m, Permissions::Lookup );
+
+// if ( !ok() || !permitted() )
+ if ( !ok() )
+ return;
+
+ bool canned=false;
+
+ if (canned) {
+ EString box = "INBOX";
+ EString ann = "/comment";
+ EString pro = "value.priv";
+ EString val =
"messaging_network-systems-solutions_net_6cf1_19682fae_17b";
+
+ EString res = "ANNOTATION " + box.quoted() + " " + ann.quoted()
+ "(" + pro.quoted() + " " + val.quoted() + ")";
+ respond(res);
+ box = "Trash";
+ res= "ANNOTATION " + box.quoted() + " " + ann.quoted() + "(" +
pro.quoted() + " " + val.quoted() + ")";
+
+ respond(res);
+ finish();
+ }
+
+ EString here = "ANNOT entrant with query phase ";
+ here.appendNumber(queryPhase);
+ log(here);
+
+ if (1 == (queryPhase % 2)) {
+ // Odd number query phases mean we're waiting for a query to
complete on the database
+ log( "ANNOT query phase " + queryPhase );
+ if (q->failed()) {
+ log( "ANNOT failed query" + q->error());
+ finish();
+ return;
+ }
+ if (q->done()) {
+ log( "ANNOT query done ");
+ queryPhase++;
+ } else {
+ log( "ANNOT query NOT done ");
+ return;
+ }
+ }
+
+
+ if (0 == queryPhase) {
+ UString absHome = imap()->user()->home()->name();
+ log( "ANNOT getannotation home start point " + absHome.ascii());
+
+ EString sql = " select
right(mb.name,length(mb.name)-length($1::text)) as imapname, "
+ " mban.name,mba.privatevalue,mba.sharedvalue "
+ " from mailboxes mb join mailbox_annotations mba
on (mb.id=mba.mailbox) "
+ " join mailbox_annotation_names mban on
(mba.name=mban.id) where (";
+
+ int sqlParamNo = 2;
+ EStringList sqlParams;
+
+ EStringList::Iterator mbiter( mailboxesRequested );
+ while ( mbiter ) {
+ EString thisBox = *mbiter;
+ int lastChar = thisBox.length() - 1;
+
+ if (thisBox.endsWith("*")) {
+ // * means wildcard - % is allowed too but
that's already set up to be
+ // convenient in SQL so we don't need to handle
it here
+ if (0 == lastChar)
+ thisBox = absHome.ascii() + "/%";
+ else
+ thisBox=absHome.ascii() + "/" +
thisBox.mid(0, lastChar-1) + "%";
+ } else {
+ thisBox=absHome.ascii()+"/"+thisBox;
+ }
+ log( "ANNOT getannotation " + thisBox );
+
+ if (sqlParamNo > 2) {
+ sql.append(" or ");
+ }
+
+ sql.append(" mb.name ilike $");
+ sql.appendNumber(sqlParamNo++);
+
+ sqlParams.append(thisBox);
+
+ ++mbiter;
+ }
+
+ sql.append(") and mban.name = any( $");
+ sql.appendNumber(sqlParamNo);
+ sql.append(" ) ");
+
+ EStringList::Iterator annotiter( annotationsRequested );
+ EString allAnnotations = "{ ";
+ bool firstTime=true;
+
+ while ( annotiter ) {
+ log( "ANNOT getannotation annot requested " +
*annotiter );
+ if (!firstTime) {
+ firstTime=false;
+ allAnnotations.append(", ");
+ }
+ allAnnotations.append(*annotiter);
+ ++annotiter;
+ }
+ allAnnotations.append(" }");
+
+ log( "ANNOT getannotation all annotations requested " +
allAnnotations );
+
+ sql.append(" order by mb.id");
+
+
+ log( "ANNOT getannotation sql " + sql);
+
+ EStringList::Iterator paramIter( sqlParams );
+ sqlParamNo = 2;
+
+ q = new Query(sql, this);
+
+ log( "ANNOT getannotation created query");
+
+ q->bind(1, absHome + "/" );
+ log( "ANNOT getannotation added param 1");
+
+ while (paramIter) {
+ log( "ANNOT getannotation binding ");
+ q->bind(sqlParamNo++, *paramIter);
+ log( "ANNOT getannotation param " + *paramIter);
+ ++paramIter;
+ }
+
+ log("ANNOT all annot " + allAnnotations);
+ q->bind(sqlParamNo, allAnnotations);
+
+ queryPhase = 1;
+ log( "ANNOT getannotation executing query");
+ q->execute();
+ }
+
+ here = "ANNOT query phase ";
+ here.appendNumber(queryPhase);
+ log(here);
+
+ if (2==queryPhase) {
+ EString response;
+ log("ANNOT checking results");
+ if (q->hasResults()) {
+ // Process The Results
+ log("ANNOT got results");
+
+ EString lastbox;
+ EString lastannot;
+
+
+
+ while (q->hasResults()) {
+ response = "ANNOTATION ";
+ Row *r = q->nextRow();
+ EString box = r->getEString("imapname");
+ EString annot = r->getEString("name");
+ EString valpriv = r->getEString("privatevalue");
+ EString valshrd = r->getEString("sharedvalue");
+ log("ANNOT result " + box + "-" + annot + "-" +
valpriv);
+
+ response.append(box.quoted());
+ response.append(" ");
+
+ // The annotation
+ response.append(annot.quoted());
+
+ response.append(" (");
+ // Any values
+ bool wantPriv = false;
+ bool wantShar = false;
+ EString pr = "value.priv";
+ EString sh = "value.shared";
+
+ if (valuesRequested.end() !=
valuesRequested.find("value")) {
+ // Both private & shared
+ wantPriv = true;
+ wantShar = true;
+ } else if (valuesRequested.end() !=
valuesRequested.find(pr)) {
+ // private
+ wantPriv = true;
+ } else {
+ // bad practice, assume shared
+ wantShar = true;
+ }
+ if (wantPriv) {
+ response.append(pr.quoted() + " " +
valpriv.quoted());
+ }
+ if (wantShar && wantPriv)
+ response.append(" ");
+ if (wantShar) {
+ response.append(sh.quoted() + " " +
valshrd.quoted());
+ }
+
+ response.append(")");
+
+ log(response);
+
+ respond(response);
+
+ lastbox = box;
+ lastannot = annot;
+
+ }
+
+ } else {
+ log("ANNOT NO results");
+ }
+
+
+ EStringList::Iterator nmiter( valuesRequested );
+ while ( nmiter ) {
+ log( "ANNOT getannotation " + *nmiter );
+ ++nmiter;
+ }
+ queryPhase=-1;
+ }
+
+
+ if (-1 == queryPhase)
+ finish();
+}
diff -wru --new-file aox-master/imap/handlers/folderannotation.h
aox-jim/imap/handlers/folderannotation.h
--- aox-master/imap/handlers/folderannotation.h 1970-01-01 00:00:00.000000000
+0000
+++ aox-jim/imap/handlers/folderannotation.h 2014-07-07 12:39:47.156613000
+0000
@@ -0,0 +1,50 @@
+// Copyright 2009 The Archiveopteryx Developers <[email protected]>
+
+#ifndef FOLDERANNOTATION_H
+#define FOLDERANNOTATION_H
+
+#include "command.h"
+
+
+class GetAnnotation
+ : public Command
+{
+public:
+ GetAnnotation();
+
+ void parse();
+ void execute();
+
+private:
+ class Query * q;
+ class Mailbox * m;
+ EStringList mailboxesRequested;
+ EStringList annotationsRequested;
+ EStringList valuesRequested;
+ int queryPhase;
+};
+
+
+class SetAnnotation
+ : public Command
+{
+public:
+ SetAnnotation();
+
+ void parse();
+ void execute();
+
+private:
+ class Query * q;
+ class Mailbox * m;
+ EString annotationName;
+ EString annotationContext;
+ EString annotationValue;
+ int queryPhase;
+ int nameID;
+ int annotID;
+ Transaction * trans;
+};
+
+
+#endif
diff -wru --new-file aox-master/imap/handlers/Jamfile
aox-jim/imap/handlers/Jamfile
--- aox-master/imap/handlers/Jamfile 2014-06-03 13:15:21.000000000 +0000
+++ aox-jim/imap/handlers/Jamfile 2014-07-06 20:26:11.528613000 +0000
@@ -37,4 +37,5 @@
thread.cpp
unselect.cpp
urlfetch.cpp
+ folderannotation.cpp
;
diff -wru --new-file aox-master/imap/handlers/sort.cpp
aox-jim/imap/handlers/sort.cpp
--- aox-master/imap/handlers/sort.cpp 2014-06-03 13:15:21.000000000 +0000
+++ aox-jim/imap/handlers/sort.cpp 2014-07-06 20:26:11.784613000 +0000
@@ -210,8 +210,8 @@
{
switch ( c->t ) {
case Arrival:
- addJoin( t, "join messages m on (m.id=mm.message) ",
- "m.idate", c->reverse );
+ addJoin( t, "join messages mardt on (mardt.id=mm.message) ",
+ "mardt.idate", c->reverse );
break;
case Cc:
addJoin( t,