[re-arranged part of the quotes for better structure of the reply, I don't 
want to answer to the same question in three different areas]

On Monday, October 28, 2013 12:29:51 AM Steve Dougherty wrote:
> >> Short summary of what event-notifications provides: Before
> >> event-notifications, a WOT client would be implemented by periodically
> >> polling WOT for identities / trusts / scores. This caused a lot of
> >> database queries and made everything slow and laggy.
> >> With event-notifications, clients of WOT subscribe to types of objects
> >> which they are interested in: Identities / Trusts / Scores / and in the
> >> future introduction puzzles. WOT will then send all known objects at
> >> time of subscripion once, and afterwards keep the client up to date by
> >> only sending single objects as they are changed (= as an event happens).
> >> This causes database
> >> and network traffic only to happen when there is an actual need for it.
> 
> This seems concerning to me. It is a very odd client application that
> wants to pull in WoT's entire view of the web of trust. I assume this
> means the initial state of all things matching a query, and
> notifications are sent whenever the results change?

Yes, when something changes, you only get notified about the object which has 
changed, not about objects of the same type which were not changed.
For example if you subscribe to the set of all identities, a change-
notification will only contain the single identity which has changed.

> Having the ability to subscribe to everything or an entire category of
> things - though very odd - I am okay with, if only because it's
> convenient for debugging. A condition of "*" or something would be fine.

> [...]

> >> Reply:
> >> The reply consists of two separate FCP messages:
> >> The first message is "Message" = "Identities" | "Trusts" | "Scores".
> >> It contains the full dataset of the type you have subscribed to.
> >> By storing this dataset, your client is completely synchronized with WOT.
> >> Upon changes of anything, WOT will only have to send
> >> the single {@link Identity}/{@link Trust}/{@link Score} object which has
> >> changed for your client to be fully synchronized again.
> 
> WoT is synchronizing entire categories of stuff? Always sending
> everything is insanity. I really think WoT should allow much more
> selectiveness. The concept of keeping state up to date with an initial
> synchronization and subsequent notifications is great, but this
> returning too much information in a way that requires a lot of
> additional processing to be useful. Requiring such processing invites
> another wave of duplicate implementations of WoT-wrangling code in clients.
> 
> Were I writing a client I think I'd be interested in WoT sending me
> updates on things like "identities with a Sone context that have a score
> higher than 0 from the perspective of local identity XYZ."

The assumption that sending the full dataset at start of a subscription is bad 
will  become the #1 FAQ entry about event-notifications once I am able to edit 
in the wiki :) - I've already had this discussion for an hour on IRC with 
someone (I think digger3), I hope that I'm able to explain it better now.
So please read carefully:

It is NOT only for debugging purposes, and it is not insane. Forget about WOT 
for a moment and think of a database as a set of objects, entities, files, 
things, whatever. 
If you have a set of things, and you want to keep synchronized copies of that 
set among different machines, you CAN keep it up-to-date by only sending single 
elements of the set as they change -  BUT you can only do that if you knew 
what the initial state was, i.e. the FULL initial set. This is not an 
implementation detail. Its logically impossible to build a full view of 
something if you ONLY get notified about changes. You HAVE TO know the initial 
state for the change-only notifications to be able to provide you with a full 
view of everything.
Lets look at this from a different example which you have worked in length 
with: Version control, for example Git.
If you update a git repository by "git pull", you WILL only download changes, 
the "diffs" of each commit. But that only works if you did a "git clone" 
before. And what does git clone do? It copies the WHOLE repository. 
Would it be possible to compile the code WITHOUT a clone, just by having the 
diffs? No way. Only having random pieces of modifications of a bunch of source 
code produces random garbage which is not equal to the full source code.

What CAN be optimized about the process of initial synchronization:
1) Re-defining what "the WHOLE dataset" means by introducing filters on 
subscriptions (not implemented yet) as you suggested. The filter will cut down 
the size of the  dataset, while still having it be a logical unit which is 
atomically  meaningful.
In version control terms this might mean that you only clone 
certain branches of a repository. A branch will compile cleanly even though it 
is only part of a project - because it is a senseful logical unit.
WOT examples A publish-only client like FlogHelper only needs to know about 
the user's own identities.  A typical WOT instance will only have a handful of 
user-owned identities, which is completely acceptable to be transferred as a 
whole. So the filter would be "Only OwnIdentity objects".
Another WOT example: Clients which plan to fetch data from remote identities 
will only want to know about the identities which WOT actually considers as 
trustworthy based upon its core goal of computing a rating ("score"). So there 
will be a filter for receiving only identities with a positive score.
2) In the near future, I should implement "persistent" subscriptions. 
Persistent in our case means that you  can restart WOT or the client which 
filed the subscription WITHOUT re-creating  a fresh subscription, and therfore 
WITHOUT a new synchronization of the  database content. 
While WOT is running but the client is not connected, WOT will just continue 
storing events for it, up to a certain timeout of lets say  1 hour, after 
which the subscription will be killed. So when the client reconnects, 
synchronization of the initial dataset does not have to happen again. It can 
instead just send out all events which happened while the client was offline.
This should be sufficient to guarantee that in normal operation of a node, 
there 
will only be ONE synchronization of the database per plugin - when loading it 
for the first time. Downtime of plugins typically only happens when the node is 
restarted or a plugin is updated, and it shouldn't be more than an hour I 
guess. The bugtracker entry for that is here: 
https://bugs.freenetproject.org/view.php?id=6113

One more thing: You could argue that introducing "filters" on the initial 
dataset is semantically identical to just allowing the client to request WOT 
to NOT send any initial dataset: You could set a filter which filters by date 
and filters out EVERYTHING before the date of subscription.
And thereby, you could request me to just implement a flag "No intial 
synchronization please" to allow the client to request no synchronization. But 
I do think that it is wise to make synchronization mandatory and require 
clients to use "filters" instead: If someone has not understood that the 
fundamental nature of synchronizing data between machines via diffs requires 
some kind of initial state synchronization, he should not yet be writing a 
client which is driven by  change-notifications. 
So having the synchronization be mandatory induces client authors to notice 
their misconception about synchronization which I just elaborated a lot about. 
Hopefully they will wind up to read a FAQ entry then :)
Further, the "filter out EVERYTHING before start of subscription" filter is 
just 
a special case of synchronization where the full dataset at start of the 
subscription is *empty*. So having filters in addition to initial 
synchronization is the most generic implementation possible.

> > The initial state could be huge. Please don't send it as part of the FCP
> > message's SimpleFieldSet. Either split it up into multiple messages
> > (e.g. one per identity; but then make sure there is some way of telling
> > that you got all of them), or (probably better) put it in a Bucket i.e.
> > a data field.
> 
> I will go further and say I think it very rarely makes sense for a
> client application to have to pull in (and keep up-to-date with!) WoT's
> entire state in the first place. I would expect queries and subscribing
> to changes in their results.

Forum systems such as Freetalk *do* want to give every identity which is 
trusted a chance to post messages, so they need to know about them. That will 
apply to most identities in the database. So synchronizing almost the full set 
of identities will happen.

> >> I had a better idea though: The end of this  mail will contain a dump of
> >> the FCP traffic between a client of event-notifications and WOT. So you
> >> can have a look at how the communication typically looks.
> >> Before that, there will be a copy-pasta of the JavaDoc of the "Subscribe"
> >> FCP function - this is the initial function which enables
> >> event-notifications. Its JavaDoc will give you a rough idea of how to
> >> interpret the following FCP dump.
> >> 
> >> NOTICE: Even though by this mail significant effort is being spent on
> >> making FCP easy to use by client authors, you should only implement an
> >> actual FCP client as a last resort: I've done the job of implementing a
> >> Java class
> >> "FCPClientReferenceImplementation" which serves the purpose of providing
> >> a
> >> reference client to event-notifications. This took around 3 weeks, is
> >> ~1000
> >> lines and very thoroughly tested. Please use it.
> 
> Very nice! I think this is an excellent layer of separation to have.

Hooray :)

> Client applications should not always have to deal with parsing text to
> do WoT things. Maybe this interface could even be cleanly replaced in
> the future with something more direct where appropriate like OSGi?

Whats visible of it from the outside of FCPClientReferenceImplementation is 
very primitive, basically
- start() / stop()
- subscribe() / unsubscribe()
- Generic single-function interface for receving the initial synchronization, 
the function is "handleSubscriptionSynchronization(Collection<T> )". T is the 
type to which you subscribe()
- Generic single-function interface for receiving change notifications. 
handleSubscribedObjectChanged(ChangeSet<T>). T again is the type to which you 
subscrived. The ChangeSet is a pair which provides a copy of the changed 
object before and after the change.
 
So it doesn't touch any specifics of FCP, almost not even those of WOT - the 
chances  of being able to use a different backend than FCP are nice.
Also, the backend implementation of event-notifications, i.e. the "server", 
isn't hardcoded to only work with FCP, the type of client is already 
abstracted into an enum, and FCP is just one of the types.

> >> FCP "Subscribe" JavaDoc (removed some uninteresting parts):
> >> -------------------------------------------------------------------------
> >> ----- Processes the "Subscribe" FCP message, filing a {@link
> >> Subscription} to event-{@link Notification}s via {@link
> >> SubscriptionManager}.
> 
> So far so good.
> 
> >> Required fields:
> >> "To" = "Identities" | "Trusts" | "Scores" - chooses among {@link
> >> IdentitiesSubscription} / {@link TrustsSubscription} /
> >> {@link ScoresSubscription}.
> 
> It took me a few minutes to understand that this means that the "To"
> field can be one of the values "Identities", "Trusts", or "Scores". It'd
> be helpful if it was clearer somehow. Maybe: To =
> [Identities|Trusts|Scores] ? Maybe just English.

I've fixed the JavaDoc to use "or". Thank you.

> Is my understanding correct that this is an internal WoT function for
> parsing FCP from clients, and not a part of the reference event
> notifications client? It's just for understanding the FCP fields in use?

It is an internal function of the FCP server, yes. Currently, we have no 
separate place for documenting the API of FCP calls. Instead, people who want 
to write a FCP client are told to read the JavaDoc of the internal FCP server 
functions which handle the FCP calls which they want to use.

> >> This message is send via the synchronous FCP-API: You can signal that
> >> processing it failed by returning an error in the FCP message processor.
> >> This allows your client to be programmed in a transactional style: If
> >> part of the transaction which
> >> stores the dataset fails, you can just roll it back and signal the error
> >> to
> >> WOT. It will rollback the subscription then and
> >> send an "Error" message, indicating that subscribing failed. You must
> >> file
> >> another subscription attempt then.
> 
> This sounds like it's just for debugging purposes. Is there a way in
> which failure to apply an incremental change to the state is not a bug?

No, its NOT just for debugging.
Transactional programming means that you assume that the contents of your 
database are always 100% valid in terms of logically making sense. 
You are free to do partial changes to the database which cause a non-senseful 
state for as long as after the transaction the database is 100% semantically 
correct again. Disruptive changes are always wrapped in a transaction, and the 
transaction is either commited fully or thrown away completely. 
So transactions are atomic.
Leaving out a single part of a event-notifications subscription chain means 
that the semantic integrity of the client would be damaged.:The data does not 
match the actual WOT database contents anymore if you leave out the initial 
syncronization or an event in the event-chain!

The good thing about transactions it that they can guard against failure of 
LOTS of things in a complex process. If a minor very low level detail of low 
level code fails, you just throw out an exception to the high level code. It 
will rollback() the transaction, and event-notifications will cause it to 
happen again. While progamming transactionally, you basically assume that any 
code can fail, even if you cannot come up with an example of WHY it might 
fail. . 
The core point I'm trying to make is that the "top level" code which actually 
initiates the transactions is what makes that assumption. It considers the 
actual workers which do the processing of the transaction as little magic 
black boxes which can fail for weird reasons, and are free to do so. It might 
be out of memory, or whatever. You throw one worker away, and let the next try 
to do the transaction again. The top level code in our case is the event-
notifications server of WOT: It allows event-processors to fail, and can resend 
the event then.
A simple example of something which can and will happen in practice: Shutdown. 
At shutdown, there might be threads running to process event-notifications. 
Some of them might be in "waiting" state, waiting for a lock on the database 
where they store identities/trusts/scores. The database might be terminated in 
between, so the waiting event-processors cannot process the event. As event-
notifications might be persistent in the future, it is necessary that WOT 
resents the aborted event-notification as the client plugin is started again.

> >> The second message is formatted as:
> >> "Message" = "Subscribed"
> >> "SubscriptionID" = Random {@link UUID} of the Subscription.
> >> "To" = Same as the "To" field of your original message.
> 
> I'm curious why this ID is not in the first message? Why two messages?

If you indicate failure of processing the intial synchronization message, you 
are not subscribed because you don't have the full dataset available and it is 
impossible to obtain it without the initial synchronization.

> >> Errors:
> >> If you are already subscribed to the selected type, you will only receive
> >> a
> >> message:
> >> "Message" = "Error"
> >> "Description" =
> >> "plugins.WebOfTrust.SubscriptionManager$SubscriptionExistsAlreadyExceptio
> >> n"
> >> "SubscriptionID" = Same as in the original "Subscribed" message
> >> "To" = Same as you requested
> >> "OriginalMessage" = "Subscribe"
> 
> Would it be appropriate to have an error code so that some error types
> can be machine-readable? I remember running into difficulties with this
> sort of thing with my Infocalypse patches.

The "Description" is the actual class name of the exception, so it should be 
safe to assume that the same literal value is always used.

> >> {@link Notification}s:
> >> Further  messages will be sent at any time in the future if an {@link
> >> Identity} / {@link Trust} / {@link Score}
> >> object has changed. They will contain the version of the object before
> >> the
> >> change and after the change.
> 
> Is there a reason to contain the before? Is it to relieve the client of
> having to store all values to see how much the change was?

I didn't have any specific use in mind, I just thought that it was easy to 
implement and nice to have.
One example for its use might be: It allows the client to easily monitor 
special event types which it has invented on its own and which are constituted 
by a certain member value of identity/trust/score having changed. For example 
you might be interested in a certain "property" of an identity having changed. 
Remember: Identity "properties" are key/value pairs which are intented as 
primitive storage for client applications. For example FlogHelper stores which 
flogs an identity publishes in those k/v pairs. A client might want to notice 
you once your favorite identities publish a new flog.

Also, for debugging purposes, it allows to check your own database contents 
with every message. This *is* implemented by DebugFCPClient.

> >> These messages are also send with the synchronous FCP API. In opposite to
> >> the initial synchronization message, by replying with failure to the
> >> synchronous FCP call, you can signal that you want to receive the same
> >> notification again.
> This is inconsistent,

It is, and that annoyed my while implementing it. I would rather have both the 
synchronization and the event-notifications be automatically resent. But 
automatic resending of the initial synchronization would be difficult to 
implement. Also, it is a LARGE dataset and therefore we should avoid sending 
it too often.

> and it is confusing to me. If there is not an
> error during transmission what could resending a message achieve? The
> client already got the message.

As explained above, the goal is to allow transactional programming in the 
client. A transactional, and as such in this case especially database-based 
client ONLY keeps its transactional database as storage: One of the point of 
using a database is to be able to store data which is so large that it doesn't 
fit into memory. Therefore, a truly database-based client doesn't keep ANYTHING 
in memory permanently. If an event causes the need for it do to something, it 
only queries from the database what is needed to process the event - which 
will fit into memory. Afterwards processing finishes, all in memory objects are 
flushed. For example consider forum systems such as Freetalk: If the user wants 
to display the threads in a forum, you only query the first 50 threads in the 
forum and display them as page 1. You do NOT load the other 1000000 pages into 
memory, they wouldn't fit and the user isn't viewieng them now anyway.

So if the transaction aimed at storing it to the database fails, there is no 
other place where the client can store it to retry the transaction, because 
conceptually it shall not keep ANYTHING in memory permanently. If it did keep 
a queue of failed event-notifications in memory, what happens if 100000 of them 
fail in a row? Out of memory! What happens if they had initially failed due to 
out of memory? Out of memory ^ 2.
The source of information which triggered the transaction must re-trigger it 
if it failed because it has stored its existence anyway. In our case the 
source is WOT.

> >> After a typical delay of {@link
> >> SubscriptionManager#PROCESS_NOTIFICATIONS_DELAY}, it will be re-sent.
> >> There is a maximal amount of {@link
> >> SubscriptionManager#DISCONNECT_CLIENT_AFTER_FAILURE_COUNT} failures per
> >> FCP- Client.
> >> If you exceed this limit, your subscriptions will be terminated. You will
> >> receive an "Unsubscribed" message then as long as your client has not
> >> terminated the FCP connection. See {@link
> >> #handleUnsubscribe(SimpleFieldSet)}. The fact that you can request a
> >> notification to be re-sent may also be used to program your client in a
> >> transactional style.
> >> If the transaction which processes an event-notification fails, you can
> >> indicate failure to the synchronous FCP sender and
> >> WOT will then re-send the notification, causing the transaction to be
> >> retried.
> The notion of resending enabling transactional state changes seems at
> odds with being unsubscribed after hitting a limit on them. 

This is necessary to guard against bugs which cause WOT not noticing that a 
client has disconnected. If the client has disconnected, its notifications' 
resinding will fail continously and WOT will just disconnect it due to that 
then.
If there was no such mechanism, ghost clients would cause the database to grow 
indefinitely from notifications which cannot be deployed.
 
> >> If your client is shutting down or not interested in the subscription
> >> anymore, you should send an "Unsubscribe" message.
> >> See {@link #handleUnsubscribe(SimpleFieldSet)}. This will make sure that
> >> WOT stops gathering data for your subscription,
> >> which would be expensive to do if its not even needed. But if you cannot
> >> send the message anymore due to a dropped connection,
> >> the subscription will be terminated automatically after some time due to
> >> notification-deployment failing. Nevertheless,
> >> please always unsubscribe when possible.
> 
> Does WoT not have visibility on when FCP connections close?
> How does WoT
> handle pushing messages over FCP? I had to jump through hoops to do that
> with Infocalypse.

What it keeps as a "connection" for being able to send to the client in a 
pushing manner is WeakReference<PluginReplySender> . PluginReplySender is the 
object which you get when a plugin uses a PluginTalker to send a message to 
you as a server. In other words: The client uses PluginTalker to send the 
original message to the server. The server's message handler gets called by 
the node, and the node gives it a PluginReplySender for being able to answer. 
By keeping a WeakReference<PluginReplySender>, WOT can keep the sender in 
memory for being able to deploy notifications in the future. Because it is a 
WeakReference, it will get garbage-collected if the client drops its 
PluginTalker. WOT also montiors a ReferenceQueue on the WeakReference objects, 
which allows it to notice if one of their pointers got GC'ed, and purge the 
WeakReference object itself.
However, this is not immediate, and it is not a strong mechanism - GC might 
happen very far in the future. 

So to answer the original question: The PluginTalker API just does not support 
explicit disconnection. I decided it would be easier to just deal with it as 
is than changing it.
Further, network connections can randomly drop dead, so you need to assume 
that both graceful disconnection as well as random death (= timeout) can 
happen. The "Unsubscribe" message is the graceful disconnection. The exceeding 
of the event-notification failure counter deals with random death.
Graceful disconnection is usually implemented in addition to timeout for 
performance reasons: The quicker we get told that the client is disconnected, 
the quicker we can stop gathering data for it. So that's why you are also able 
to unsubscribe.

> 
> >> -------------------------------------------------------------------------
> >> -----
> >> 
> >> FCP dump of a typical connection which subscribes to all types of
> >> objects::
> >> (Notice that the duplicate fields are for backwards compatibility with
> >> old
> >> clients.
> >> They are present even in event-notifications because the functions for
> >> generating
> >> FCP data are re-used in different areas of code.)
> >> -------------------------------------------------------------------------
> >> ----- ---------------- Fri Oct 25 02:14:07 CEST 2013 Connected.
> >> ---------------- ---------------- Fri Oct 25 02:14:07 CEST 2013 Sent:
> >> ---------------- Message=Subscribe
> >> To=Identities
> >> End
> 
> I guess it makes sense that it needs no more information if there are
> three predetermined things a client can subscribe to.

?

> >> ---------------- Fri Oct 25 02:14:08 CEST 2013 Received: ----------------
> >> Message=Identities
> >> Identities.Amount=5
> >> Identities.0.CurrentEditionFetchState=NotFetched
> >> Identities.0.ID=QeTBVWTwBldfI-lrF~xf0nqFVDdQoSUghT~PvhyJ1NE
> >> Identities.0.Identity=QeTBVWTwBldfI-lrF~xf0nqFVDdQoSUghT~PvhyJ1NE
> >> Identities.0.PublishesTrustList=true
> >> Identities.0.RequestURI=USK@QeTBVWTwBldfI-
> >> lrF~xf0nqFVDdQoSUghT~PvhyJ1NE,OjEywGD063La2H-
> >> IihD7iYtZm3rC0BP6UTvvwyF5Zh4,AQACAAE/WebOfTrust/1344
> >> Identities.0.Type=Identity
> >> Identities.0.Contexts.Amount=0
> >> Identities.0.Properties.Amount=0
> >> Identities.1.CurrentEditionFetchState=NotFetched
> >> Identities.1.ID=D3MrAR-AVMqKJRjXnpKW2guW9z1mw5GZ9BB15mYVkVc
> >> Identities.1.Identity=D3MrAR-AVMqKJRjXnpKW2guW9z1mw5GZ9BB15mYVkVc
> >> Identities.1.PublishesTrustList=true
> >> Identities.1.RequestURI=USK@D3MrAR-
> >> AVMqKJRjXnpKW2guW9z1mw5GZ9BB15mYVkVc,xgddjFHx2S~5U6PeFkwqO5V~1gZngFLoM-
> >> xaoMKSBI8,AQACAAE/WebOfTrust/4959
> >> Identities.1.Type=Identity
> >> Identities.1.Contexts.Amount=0
> >> Identities.1.Properties.Amount=0
> >> Identities.2.CurrentEditionFetchState=NotFetched
> >> Identities.2.ID=s88mAwLB3OW6mYlZ43XaHDM1K6QXosZ4QTt2UX-hq6s
> >> Identities.2.Identity=s88mAwLB3OW6mYlZ43XaHDM1K6QXosZ4QTt2UX-hq6s
> >> Identities.2.PublishesTrustList=true
> >> Identities.2.RequestURI=USK@s88mAwLB3OW6mYlZ43XaHDM1K6QXosZ4QTt2UX-
> >> hq6s,555tpw1TUReXUixAMDQD3RcD6gUKwOBCDQ6Dot2v6qg,AQACAAE/WebOfTrust/5
> >> Identities.2.Type=Identity
> >> Identities.2.Contexts.Amount=0
> >> Identities.2.Properties.Amount=0
> >> Identities.3.CurrentEditionFetchState=NotFetched
> >> Identities.3.ID=z9dv7wqsxIBCiFLW7VijMGXD9Gl-EXAqBAwzQ4aq26s
> >> Identities.3.Identity=z9dv7wqsxIBCiFLW7VijMGXD9Gl-EXAqBAwzQ4aq26s
> >> Identities.3.PublishesTrustList=true
> >> Identities.3.RequestURI=USK@z9dv7wqsxIBCiFLW7VijMGXD9Gl-
> >> EXAqBAwzQ4aq26s,4Uvc~Fjw3i9toGeQuBkDARUV5mF7OTKoAhqOA9LpNdo,AQACAAE/WebOf
> >> Trust/1270 Identities.3.Type=Identity
> >> Identities.3.Contexts.Amount=0
> >> Identities.3.Properties.Amount=0
> >> Identities.4.CurrentEditionFetchState=NotFetched
> >> Identities.4.ID=o2~q8EMoBkCNEgzLUL97hLPdddco9ix1oAnEa~VzZtg
> >> Identities.4.Identity=o2~q8EMoBkCNEgzLUL97hLPdddco9ix1oAnEa~VzZtg
> >> Identities.4.PublishesTrustList=true
> >> Identities.4.RequestURI=USK@o2~q8EMoBkCNEgzLUL97hLPdddco9ix1oAnEa~VzZtg,X
> >> ~vTpL2LSyKvwQoYBx~eleI2RF6QzYJpzuenfcKDKBM,AQACAAE/WebOfTrust/9379
> >> Identities.4.Type=Identity
> >> Identities.4.Contexts.Amount=0
> >> Identities.4.Properties.Amount=0
> >> End
> 
> It seems evident that actual responses will be orders of magnitude larger.

Yes.

> Why are both Identity and ID specified as the same thing? I was under
> the impression is is an identity ID - would just ID be okay? 
> IIRC it's already not going to be backwards-compatible with existing clients 
> due to the full stops between key segments.

Yes it is for backwards compatibility even though it still doesn't match what 
existing clients expect:
The low-level backwards-comptaible code which produces the stuff after the dot 
does not know about the high-level stuff which adds the dot.
Basically, there is a function "addIdentityFields(..., String prefix, String 
suffix)". Of course I could have chosen to have the function contain some weird 
logic which only adds certain fields for certain prefix/suffix combinations but 
I 
think thats ugly because it doesn't cleanly separate code into different areas 
of concern. Arguably you already did this with part of the function, but I 
really didn't want it to get even more complicated.

>  What are Contexts.Amount and Properties.Amount for? The number of each
> that are given? I think "Count" might be a clearer name.

I have been using "amount" as a synonym for "count" for years :( 
Is it really not a synonym?
I'm not a English native speaker, sorry.

> >> ---------------- Fri Oct 25 02:15:20 CEST 2013 Received: ----------------
> >> Message=IdentityChangedNotification
> >> AfterChange.Context0=Introduction
> 
> I think a key of AfterChange.Context.0 would be preferable for
> consistent separation of the number. Why isn't there an
> AfterChange.Context (without the trailing 0) like the other fields?

Legacy as well. orry. I should have stripped all  legacy stuff from the 
original message. The most recent code path produces the 
following (which you can see below is also part of the original message):
"AfterChange.Identities.0.Contexts.0.Name=Introduction"

> >> AfterChange.CurrentEditionFetchState=Fetched
> >> AfterChange.CurrentEditionFetchState0=Fetched
> 
> I'm confused why these and others are duplicated save for a 0 on the end
> of the key. 

Both those fields are also legacy. We have 3 syntaxes of which 2 are legacy. 
The one without 0 is from the oldest code path. Then someone suggested to 
number everything so parsers can eat both versions (you?). Then it was 
suggested to split everything with a dot.

> Where are the meanings of the possible values for fields
> like this documented?

I was reluctant to bloat the "Subscribe" documentation with full documentation 
of the Identity/Trust/Score syntax. So there currently is no documentation :| 
You could enforce me to write one at the underlying functions which generate 
the SFS data by asking me to do it.

But it can be clearly seen what is the most recent syntax by looking at the 
reference parser implementations IdentityParser / TrustParser / ScoreParser 
which are member classes of FCPClientReferenceImplementation:
https://github.com/freenet/plugin-WoT-
staging/blob/master/src/plugins/WebOfTrust/ui/fcp/FCPClientReferenceImplementation.java


> >> AfterChange.ID=WNOyZsnZtpFjwmwfVBqC1PhSeg-hErXHlkrR43h0tiU
> >> AfterChange.ID0=WNOyZsnZtpFjwmwfVBqC1PhSeg-hErXHlkrR43h0tiU
> >> AfterChange.Identity=WNOyZsnZtpFjwmwfVBqC1PhSeg-hErXHlkrR43h0tiU
> >> AfterChange.Identity0=WNOyZsnZtpFjwmwfVBqC1PhSeg-hErXHlkrR43h0tiU
> >> AfterChange.InsertURI=USK@AILRi~9nfD2pesTkeDvwZxe3cRmkY7Q00CUxQyUOVW-
> >> H,GzEIwcFQ78J7-RCzxgvY4Pfq0T8Lm4v0BazjMtkqT~8,AQECAAE/WebOfTrust/0
> >> AfterChange.InsertURI0=USK@AILRi~9nfD2pesTkeDvwZxe3cRmkY7Q00CUxQyUOVW-
> >> H,GzEIwcFQ78J7-RCzxgvY4Pfq0T8Lm4v0BazjMtkqT~8,AQECAAE/WebOfTrust/0
> >> AfterChange.Nickname=Alexandre_Umpleby
> >> AfterChange.Nickname0=Alexandre_Umpleby
> >> AfterChange.PublishesTrustList=true
> >> AfterChange.PublishesTrustList0=true
> >> AfterChange.RequestURI=USK@WNOyZsnZtpFjwmwfVBqC1PhSeg-
> >> hErXHlkrR43h0tiU,GzEIwcFQ78J7-
> >> RCzxgvY4Pfq0T8Lm4v0BazjMtkqT~8,AQACAAE/WebOfTrust/0
> >> AfterChange.RequestURI0=USK@WNOyZsnZtpFjwmwfVBqC1PhSeg-
> >> hErXHlkrR43h0tiU,GzEIwcFQ78J7-
> >> RCzxgvY4Pfq0T8Lm4v0BazjMtkqT~8,AQACAAE/WebOfTrust/0
> >> AfterChange.Type=OwnIdentity
> >> AfterChange.Type0=OwnIdentity
> 
> Would this API change be a place to change the name to LocalIdentity?

$ grep -R OwnIdentity WebOfTrust/src | wc -l
508

I don't think that we can/should ever change it. Are you 100% sure that it 
doesn't make any sense in terms of the English language?
 
> >> AfterChange.Contexts.Amount=1
> >> AfterChange.Contexts.0.Name=Introduction
> >> AfterChange.Contexts0.Amount=1
> >> AfterChange.Contexts0.Context0=Introduction
> 
> I notice - for example - "Contexts" is plural and in the initial sync
> message "Identity" is singular.

I cannot find the singular you are talking about.

> It'd make sense to me to make the
> components consistently one way or another. I have a preference for
> singular because it's shorter.

It is plural to:
- because the code contains something like 
"subscribeTo(SubscriptionType.Identities)" and I did want the enum names to 
match the string literals and "subscribeTo(Identity)" makes less sense
- it DOES contain multiple identities.

> >> AfterChange.Identities.Amount=1
> >> AfterChange.Identities.0.Context0=Introduction
> >> AfterChange.Identities.0.CurrentEditionFetchState=Fetched
> >> AfterChange.Identities.0.ID=WNOyZsnZtpFjwmwfVBqC1PhSeg-hErXHlkrR43h0tiU
> >> AfterChange.Identities.0.Identity=WNOyZsnZtpFjwmwfVBqC1PhSeg-hErXHlkrR43h
> >> 0tiU
> >> AfterChange.Identities.0.InsertURI=USK@AILRi~9nfD2pesTkeDvwZxe3cRmkY7Q00
> >> CUxQyUOVW-
> >> H,GzEIwcFQ78J7-RCzxgvY4Pfq0T8Lm4v0BazjMtkqT~8,AQECAAE/WebOfTrust/0
> >> AfterChange.Identities.0.Nickname=Alexandre_Umpleby
> >> AfterChange.Identities.0.PublishesTrustList=true
> >> AfterChange.Identities.0.RequestURI=USK@WNOyZsnZtpFjwmwfVBqC1PhSeg-
> >> hErXHlkrR43h0tiU,GzEIwcFQ78J7-
> >> RCzxgvY4Pfq0T8Lm4v0BazjMtkqT~8,AQACAAE/WebOfTrust/0
> >> AfterChange.Identities.0.Type=OwnIdentity
> >> AfterChange.Identities.0.Contexts.Amount=1
> >> AfterChange.Identities.0.Contexts.0.Name=Introduction
> 
> The ".Name" part seems unnecessary to me.

Mmh I think that is too little of a change for doing actual touching of the 
code. And contexts do might receive additional attributes once more complex 
stuff is implemented such as per-context trust.
[Per-context trust might allow you to rate the behavior of identities only for 
certain client applications. While it would be an optimization in some areas, 
it would probably be a full rewrite of the most of WOT, and is unlikely to 
happen. It has been demanded by Matthew rather often though for the 
performance impact. I think it would overcomplicate things. Further, either 
someone is a spammer or he isn't. If a person spams in one client application, 
why would you want to trust him in another?]

> >> AfterChange.Identities.0.Properties.Amount=1
> >> AfterChange.Identities.0.Properties.0.Name=IntroductionPuzzleCount
> >> AfterChange.Identities.0.Properties.0.Value=10
> 
> How about AfterChange.Identity.0.Property.IntroductionPuzzleCount=10 ?

Mmh nice catch. Properties are key/value pairs, SFS as well, so you suggest to 
use the key as the key and the value as the value - good idea.

But it is susceptible to mismatch of allowed string contents in the key-space 
of properties and the key-space of SFS. 
Typically, key/value implementations chose well-definied, small allowed spaces 
for keys, while allowing arbitrary crap in the values. So the as-is code 
guards againts SFS being more restrictive in key-space than properties by only 
using a well-defined key of "Name" / "Value", while shoving all user-definied 
data into the value only. Remember, properties are downloaded from the 
network, their keys are at arbitrary choice of the users. There is some 
limiting, but I still would have to review it.

> >> AfterChange.Identities.0.Property0.Name=IntroductionPuzzleCount
> >> AfterChange.Identities.0.Property0.Value=10
> >> AfterChange.Properties.Amount=1
> >> AfterChange.Properties.0.Name=IntroductionPuzzleCount
> >> AfterChange.Properties.0.Value=10
> >> AfterChange.Properties0.Amount=1
> >> AfterChange.Properties0.Property0.Name=IntroductionPuzzleCount
> >> AfterChange.Properties0.Property0.Value=10
> >> AfterChange.Property0.Name=IntroductionPuzzleCount
> >> AfterChange.Property0.Value=10
> >> BeforeChange.Type=Inexistent
> >> BeforeChange.Type0=Inexistent
> >> BeforeChange.Identities.0.Type=Inexistent
> 

> Why isn't there a BeforeChange for every AfterChange?

Because it "inexistent" that the whole object did not exist, not that a member 
value did not exist. It means that before there was no such identity / trust / 
score before (or after).
So I chose one arbitrary field to indicate that the whole object didn't exist. 
I used the one which sounded the most synonymous with the whole object while 
also being a field which can never be null for an existing object. Identity 
objects always have a "Type", trusts and scores always have a "Value", so I 
chose those for indicating inexistance.

If you can come up with a field name which isn't used for any actual data and 
is good for indicating whether it exists or not, suggest it please. Before you 
think about it, read my reply to that please:

> It looks like "inexistent" is actually a word, but it's not one that I
> knew. I'd have expected "nonexistent."
> T his may be a place where
> including the "before" value can lead to odd corner cases, and I wonder
> if it's worth including. Instead of a word, would it make sense to have
> an empty string?

Maybe I should just set "BeforeChange.Identities.Amount=0" to make inexstance 
very clear, eliminating the whole need for any attribute fields 
BeforeChange.Identities.0.* of the non-existant identity?

Thanks for your long review and for reading my very long reply :)
_______________________________________________
Devl mailing list
[email protected]
https://emu.freenetproject.org/cgi-bin/mailman/listinfo/devl

Reply via email to