Access to the Gmail unique message ID through Java Mail - groovy

I am trying to get the unique message ID through JavaMail, so that I can identify mail without the context of a particular IMAP-folder later. The code I've written so far is as follows:
def fp = new FetchProfile()
fp.add(FetchProfile.Item.ENVELOPE)
fp.add(FetchProfile.Item.FLAGS)
fp.add(FetchProfile.Item.CONTENT_INFO)
fp.add(javax.mail.UIDFolder.FetchProfileItem.UID)
fp.add("X-mailer")
fp.add("X-GM-MSGID") // Attribute for retrieving the ID in the fetch (?)
uIDFolder.fetch(iMAPmessages, fp);
iMAPmessages.each { msg ->
println msg.dump()
}
Which results in the following output (for one message):
<com.sun.mail.imap.IMAPMessage#14c04449
bs=com.sun.mail.imap.protocol.BODYSTRUCTURE#5ac31f43
envelope=com.sun.mail.imap.protocol.ENVELOPE#40399642
items=[:]
receivedDate=Mon Jul 28 12:18:38 MSK 2014
size=3147
peek=false
uid=991
modseq=-1
sectionId=null
type=null
subject=null
description=null
headersLoaded=false
loadedHeaders=[X-GM-MSGID:X-GM-MSGID, X-MAILER:X-mailer]
dh=null
content=null
contentStream=null
headers=javax.mail.internet.InternetHeaders#4b7e7ff
flags=com.sun.mail.imap.protocol.FLAGS#20
modified=false
saved=true
cachedContent=null
strict=true
msgnum=372
expunged=false
folder=INBOX
session=javax.mail.Session#4f0dc86c>
The Envelope:
<com.sun.mail.imap.protocol.ENVELOPE#34ea0af2
msgno=371
date=Sun Jul 27 17:46:08 MSK 2014
subject=Re: Require
from=[From <from#gmail.com>]
sender=[sender <sender#gmail.com>]
replyTo=[From <from#gmail.com>]
to=[receiver <receiver#gmail.com>, =?ISO-8859-1?Q?Johan_W=E4ngl=F6f?= <example#gmail.com>]
cc=null
bcc=null
inReplyTo=<CAHr+Gay-ZX0U-pFuwQUMziyECTOVrLNn-XLtc643DYXwgtdDFQ#mail.gmail.com>
messageId=<CAHr+GawnCxaT476K8Rm1MeQNPx8HOfL+VqM0ThMydfTNR12rBg#mail.gmail.com>>
The last attribute added to the fetch profile is the special attribute to fetch the unique ID (https://developers.google.com/gmail/imap_extensions#access_to_the_gmail_unique_message_id_x-gm-msgid).
The messages contains no unique message ID, as far as I can tell. Am I doing something completely wrong?
Trace of the IMAP Session
* 372 FETCH (UID 991 RFC822.SIZE 3147 INTERNALDATE "28-Jul-2014 08:18:38 +0000" FLAGS (\Seen) ENVELOPE ("Mon, 28 Jul 2014 10:18:38 +0200" "Hej du" (("Emil Tholin" NIL "emtholin" "gmail.com")) (("Emil Tholin" NIL "emtholin" "gmail.com")) (("Emil Tholin" NIL "emtholin" "gmail.com")) (("Emil Tholin" NIL "emtholin" "gmail.com")) NIL NIL NIL "<CADsZLRxveuyQX5gXVHvr8ca57yCLopJEc+TpuL2GS-57Kq+yqg#mail.gmail.com>") BODYSTRUCTURE ((("TEXT" "PLAIN" ("CHARSET" "UTF-8") NIL NIL "7BIT" 13 1 NIL NIL NIL)("TEXT" "HTML" ("CHARSET" "UTF-8") NIL NIL "7BIT" 34 1 NIL NIL NIL) "ALTERNATIVE" ("BOUNDARY" "001a11c1250ef810aa04ff3c91b5") NIL NIL)("TEXT" "PLAIN" ("CHARSET" "US-ASCII" "NAME" "rygg.txt") NIL NIL "BASE64" 2138 0 NIL ("ATTACHMENT" ("FILENAME" "rygg.txt")) NIL) "MIXED" ("BOUNDARY" "001a11c1250ef810af04ff3c91b7") NIL NIL) BODY[HEADER.FIELDS (X-mailer X-GM-MSGID)] {4}
)
A4 OK Success
A5 CLOSE
A5 OK Returned to authenticated state. (Success)
DEBUG IMAPS: added an Authenticated connection -- size: 1
specificFolderHeaders() - Closing the mail connection
A6 LOGOUT
* BYE LOGOUT Requested
A6 OK 73 good day (Success)
DEBUG IMAPS: IMAPStore connection dead
DEBUG IMAPS: IMAPStore cleanup, force false
DEBUG IMAPS: IMAPStore cleanup done

Use the Gmail-specific APIs in the latest release of JavaMail.

Bill hit the nail on the head, but I just thought I'd share the minor issues I faced.
The code I posted in my question didn't change much:
def messages = []
def fp = new FetchProfile()
fp.add(FetchProfile.Item.ENVELOPE)
fp.add(FetchProfile.Item.FLAGS)
fp.add(javax.mail.UIDFolder.FetchProfileItem.UID)
fp.add("X-mailer")
//Google specific attributes
fp.add(GmailFolder.FetchProfileItem.MSGID)
fp.add(GmailFolder.FetchProfileItem.THRID)
uIDFolder.fetch(iMAPmessages, fp);
iMAPmessages.each { msg ->
GmailMessage gmsg = (GmailMessage) msg
println Long.toHexString(gmsg.getMsgId().toLong()) //Eureka!
}
The part that took some time to get right was to connect to gimap with an authorization token properly:
def connect(mailAccount) {
if (mailAccount.provider == "Gmail") {
Properties props = new Properties();
props.put("mail.store.protocol", "gimaps");
props.put("mail.gimaps.sasl.enable", "true"); // Note the "gimaps"
props.put("mail.gimaps.sasl.mechanisms", "XOAUTH2");
props.put(OAuth2SaslClientFactory.OAUTH_TOKEN_PROP, mailAccount.token);
Session session = Session.getDefaultInstance(props, null);
final String emptyPassword = ""; // Since we authorize with token
GmailStore store = (GmailStore) session.getStore("gimaps");
store.connect(
"imap.gmail.com",
993,
mailAccount.emailAddress,
emptyPassword);
return store;
}
else {
OAuth2Authenticator.initialize();
def store = OAuth2Authenticator.connectToImap(
"imap.gmail.com",
993,
mailAccount.emailAddress,
mailAccount.token,
false); // Debug flag
return store;
}
}
Using a separate store for the Gmail-addresses and the neat OAuth2Authenticator (https://code.google.com/p/google-mail-oauth2-tools/source/browse/trunk/java/com/google/code/samples/oauth2/OAuth2Authenticator.java?r=3) for other providers worked great.

Related

How to catch null value on http response

I have the following code method which is used to test for an existing user in MSGraph API
public String getGuestUserId(String AuthToken,String userEmail){
String _userId
def http = new HTTPBuilder(graph_base_user_url + "?")
http.request(GET) {
requestContentType = ContentType.JSON
//uri.query = [ $filter:"mail eq '$userEmail'"].toString()
uri.query=[$filter:"mail eq '$userEmail'"]
headers.'Authorization' = "Bearer " + AuthToken
response.success = { resp, json ->
//as the retunr json alue is an array collection we need to get the first element as we request all time one record from the filter
**_userId=json.value[0].id**
}
// user ID not found : error 404
response.'404' = { resp ->
_userId = 'Not Found'
}
}
_userId
}
This method works fine when the user is existing and will return properly from the success response the user ID property.
The issue I get is that if the user is not existing, the ID field is not existing either and the array is empty.
How can I handle efficiently that case and return a meaning full value to the caller like "User Does not exist"
I have try a catch exception in the response side but seems doe snot to work
Any idea how can I handle the test like if the array[0] is empty or does not contains any Id property, then return something back ?
Thanks for help
regards
It seems to be widely accepted practice not to catch NPE. Instead, one should check if something is null.
In your case:
You should check if json.value is not empty
You also should check if id is not null.
Please also note that handling exceptions in lambdas is always tricky.
You can change the code to:
http.request(GET) {
requestContentType = ContentType.JSON
uri.query=[$filter:"mail eq '$userEmail'"]
headers.'Authorization' = "Bearer " + AuthToken
if (json.value && json.value[0].id) {
response.success = { resp, json -> **_userId=json.value[0].id** }
} else {
// Here you can return generic error response, like the one below
// or consider more granular error reporting
response.'404' = { resp -> _userId = 'Not Found'}
}
}

Notification feed following Flat feed isn't showing activities

I have a notification feed like NOTIFICATIONS:userID and I have a flat feed GLOBAL:domain.
The notification feed is set up to follow the flat feed, but when I push activities to the flat feed they are not coming through to the notification feed. I can't get them to come through the react components or making the API calls directly. Any items in the notification feed come through fine, but not the flat feed.
Is there anything I would have missed when setting up the feeds to make this possible? I'm not sure why it isn't working.
Here's the code used to call getstream:
// AddNotification writes a feed notification to the provided feed.
func (c *Client) AddNotification(feedID, actor string, n *feed.Notification) error {
keys := map[string]bool{}
feeds := make([]stream.Feed, 0)
for _, s := range n.Streams {
if s == feed.STREAM_NONE {
continue
}
if _, ok := keys[s.String()]; ok {
continue
}
f, err := c.getstream.FlatFeed(s.String(), feedID)
if err != nil {
return errors.Wrapf(err, "failed to get feed %s", feedID)
}
keys[s.String()] = true
feeds = append(feeds, f)
}
extra, err := getExtraFromString(n.Content)
if err != nil {
return errors.Wrap(err, "failed to marshal extra content")
}
appliesAt, err := time.FromProtoTS(n.GetAppliesAt())
if err != nil {
return errors.Wrap(err, "failed to cast applies at time")
}
activity := stream.Activity{
Actor: actor,
Verb: n.GetVerb(),
Object: n.GetObject(),
Extra: extra,
ForeignID: n.GetIdempotentKey(),
Time: stream.Time{Time: appliesAt},
}
log.WithFields(log.Fields{
"activity": activity,
"feeds": keys,
}).Debug("sending request to stream.io")
if err = c.getstream.AddToMany(activity, feeds...); err != nil {
return errors.Wrap(err, "error while feeding to stream.io")
}
return nil
}
Just to explain the code a bit. We have a feed.Notification type that allows you to specify what we've called "streams", these are just types that represent the slugs.
In this case, I'm using the GLOBAL:domain feed, which the user's NOTIFICATION:userID feed is set up to follow.
From batch add docs:
Activities added using this method are not propagated to followers. That is, any other Feeds that follow the Feed(s) listed in the API call will not receive the new Activity.
If you're using batching, you need to specify all feeds you want to add the activity for. Another way is that you can add to feeds one by one to push to followers.

Legacy recovery (V) values in Ethereum Signatures

Ethereum signatures must conform to the secp256k1 curve R, S and V values, requiring the V value must be either 27 or 28.
I was wondering what the legacy reason for two valid V values is?
Was this caused by the Ethereum classic hardfork? Do current wallet implementations only use one of them and the other is the legacy value that carried over?
See: https://github.com/ethereum/go-ethereum/blob/55599ee95d4151a2502465e0afc7c47bd1acba77/internal/ethapi/api.go#L452-L459
// EcRecover returns the address for the account that was used to create the signature.
// Note, this function is compatible with eth_sign and personal_sign. As such it recovers
// the address of:
// hash = keccak256("\x19Ethereum Signed Message:\n"${message length}${message})
// addr = ecrecover(hash, signature)
//
// Note, the signature must conform to the secp256k1 curve R, S and V values, where
// the V value must be be 27 or 28 for legacy reasons.
//
// https://github.com/ethereum/go-ethereum/wiki/Management-APIs#personal_ecRecover
func (s *PrivateAccountAPI) EcRecover(ctx context.Context, data, sig hexutil.Bytes) (common.Address, error) {
if len(sig) != 65 {
return common.Address{}, fmt.Errorf("signature must be 65 bytes long")
}
if sig[64] != 27 && sig[64] != 28 {
return common.Address{}, fmt.Errorf("invalid Ethereum signature (V is not 27 or 28)")
}
sig[64] -= 27 // Transform yellow paper V from 27/28 to 0/1
rpk, err := crypto.Ecrecover(signHash(data), sig)
if err != nil {
return common.Address{}, err
}
pubKey := crypto.ToECDSAPub(rpk)
recoveredAddr := crypto.PubkeyToAddress(*pubKey)
return recoveredAddr, nil
}
R and S aren't sufficient to recover the public key of the signer. V provides the extra bit of information required to disambiguate. See https://ethereum.stackexchange.com/questions/15766/what-does-v-r-s-in-eth-gettransactionbyhash-mean.

Walking through a NotesViewNavigator

I posted part of this previously but thought I would start a new thread with a more complete code and problem. I am trying to walk through the categories in a NotesViewNavigator and this is the code that I am using. I have stripped it down to pretty much a minimum. The WFSUtils.sysOut just writes a message to the server console. The view has the "Do Not Display Empty categories" checked.
vw.setAutoUpdate(false);
var nav:NotesViewNavigator = vw.createViewNav();
nav.setEntryOptions(NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
nav.setBufferMaxEntries(400);
nav.setMaxLevel(0);
var rtn:java.util.TreeMap=new java.util.TreeMap();
var entry:NotesViewEntry = nav.getFirst();
var thisCat:java.util.Vector = new java.util.Vector;
try{
while (entry != null){
WFSUtils.sysOut("Entry not null");
thisCat = entry.getColumnValues();
var thisCatString = thisCat.elementAt(0).toString()
WFSUtils.sysOut("thisCat = " + thisCatString);
rtn.put(thisCatString,"Nothing");
WFSUtils.sysOut("did put " + thisCatString)
var tEntry:NotesViewEntry = nav.getNextCategory();
entry.recycle();
entry = tEntry;
tEntry.recycle();
}
viewScope.put("vsCats", rtn.keySet());
}catch(e){
WFSUtils.sysOut("Error in getCategory " + e.toString())
}
When I run this code I get the following in the server console.
25/08/2014 12:55:42 PM HTTP JVM: Entry not null
25/08/2014 12:55:42 PM HTTP JVM: thisCat = Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 12:55:42 PM HTTP JVM: did put Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 12:55:42 PM HTTP JVM: Error in getCategory Method NotesViewNavigator.getNextCategory(lotus.domino.local.ViewEntry) not found, or illegal parameters
It fails at the getNextCategory(entry) that it is not found or illegal parameter. If I change it to just getNext(entry) the console log shows:
25/08/2014 01:06:48 PM HTTP JVM: Entry not null
25/08/2014 01:06:48 PM HTTP JVM: thisCat = Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 01:06:48 PM HTTP JVM: did put Approved~Bill Fox^WFS Automated Back end Process Example
25/08/2014 01:06:48 PM HTTP JVM: Entry not null
25/08/2014 01:06:48 PM HTTP JVM: Error in getCategory Exception occurred calling method NotesViewEntry.getColumnValues()
25/08/2014 01:06:48 PM HTTP JVM: null
So it would appear to me that the var entry is getting messed up somewhere along the line. Interesting is that the getFirst works and my code functions the way I would expect it but neither getNext nor getNextCategory seems to work. Am I missing something in my code or what the getNextCategory should be doing.
Delete the line
tEntry.recycle();
This recycle() destroys your entry as both entry and tEntry point to the same Notes object.
Delete the parameter from
... nav.getNextCategory();
Your code shown in question doesn't have a parameter but the error message and your comment tell you have.
Here is what I have ended up with. What I need to do is create a series of nested Reapeat controls that function much like native Notes Categories. So the top level category is bound to a viewScope "vsCat1", the next to "vsCat2" and the last to "vsCat3" This process could be extended to handle more levels but I generally try to keep the number of categories below that. For this example the three categories are Status, Originator and Process. I have then built three views called vwDocsByStatus, vwDocsByStatusOrig, and vwDocsByStatusOrigProcess. The first column in each view is categorized. The byStatus view is simply a categorized view by Status, the StatusOrig is Status + "~" + Originator and the third Status + "~" + Originator + "~" + Process.
The collection name for the first repeat is cat1, second cat2 and third cat3. My code below is a JavaScript function that I'm going to move to a JAVA Bean but the function would be the same. So to load "vsCat1" I would call setCategory("vwDocsByStatus",""), to load "vsCat2" I would call setCategory("vwDocsByStatusOrig", cat1) which would load vsCat2 with all of the categories under cat1. calling setCategory("vwDocsByStatusOrigProcess", cat1 + "~" + cat2) will load vsCat3 with all categories under the combination cat1 ~ cat2. The limitation is that there is no expand all type of action, but I have found that in most cases that is OK.
Then in the final repeat it is bound to the domino view vwDocsByStatusOrigProcess and a computed filter on column of cat1 + "~" + cat2 + "~" + cat3
Each repeat has an expand/collapse button that sets some viewScope variables to control visibility and calls setCategory with the appropriate view and cat values. Faster method is to just get the column values from a view but that does not take into account reader fields so the return might have some category values that the user is not allowed to see. My testing this far is that this is pretty fast.
Hope this helps someone, also if you can see how to improve it feel free to make suggestions
function setCategory(appView:String , cat:String){
/*Given a categorized view retrieve the a list of values and store that
* list in a viewScope variable "vsCat" + n where n = "1" if cat is null
* if cat contains one "~" n = 2 if cat conatins two "~" n = 3
*
*/
//get appDB and the view
try{
var vw:NotesView = appProps[sessionScope.ssApplication].appDB.getView(appView);
vw.setAutoUpdate(false);
var nav:NotesViewNavigator = vw.createViewNav();
nav.setEntryOptions(NotesViewNavigator.VN_ENTRYOPT_NOCOUNTDATA);
nav.setBufferMaxEntries(400);
nav.setMaxLevel(0);
}catch(e){
WFSUtils.sysOut("Error in setCategory - " + e.toString());
return "";
break;
}
try{
var rtn:java.util.TreeSet=new java.util.TreeSet();
var entry:NotesViewEntry = nav.getFirst();
if (cat.indexOf("~") > 0) {
n = 3;
}else if (cat == null || cat == ""){
n = 1;
}else {
n = 2;
}
var catArray:Array = cat.split("~");
var thisCat:Array = new Array;
while (entry != null && !entry.isTotal()) {
thisCat = entry.getColumnValues();
var temp:String = thisCat[0].toString();
thisCat = temp.split("~");
if (typeof thisCat === "string"){
thisCat = [thisCat];
}
switch (n){
case 1 :
if (!rtn.contains(thisCat[0])){
rtn.add(thisCat[0]);
}
break;
case 2 :
if (cat == thisCat[0]){
if (thisCat[1] != null){
if (!rtn.contains(thisCat[1]))rtn.add(thisCat[1]);
}
}
break;
case 3 :
if (cat == thisCat[0] + "~" + thisCat[1]){
if (thisCat[2] != null){
if (!rtn.contains(thisCat[2]))rtn.add(thisCat[2]);
}
}
break
}
var tmpentry:NotesViewEntry = nav.getNextCategory();
entry.recycle();
entry = tmpentry;
}// end while
// set the viewScope variable
vw.setAutoUpdate(true);
viewScope.put("vsCat" + n.toString(),rtn);
}catch(e) {
WFSUtils.sysOut("Error in setCategory - " + e.toString());
}finally{
try{
WFSUtils.recycleObjects([nav, entry, tmpEntry]);
}catch (e) {
//do nothing caused by undefined object shouldn't happen
}
}
}

CouchDB error on specific document: "function_clause error in HTTP request"

I have a CouchDB database in which I store documents with PDF and PNG attachments. After saving an attachment on a particular document, the show function fails when trying to view that document in the browser. The message in the browser is:
{"error":"unknown_error","reason":"function_clause"}
The error in couch.log is
[Wed, 08 Jun 2011 21:29:58 GMT] [error] [<0.106.0>] function_clause error in HTTP request
[Wed, 08 Jun 2011 21:29:58 GMT] [info] [<0.106.0>] Stacktrace: [{couch_httpd,doc_etag,
[{'EXIT',
{{case_clause,
{[{<<"author">>,
[<<"Brian St. Claire-King">>,
<<"Eloy LaSanta">>]},
{<<"book_type">>,[<<"Roleplaying Game">>]},
{<<"date_added">>,[2011,6,7]},
{<<"date_published">>,2008},
{<<"genre">>,
[<<"Futuristic">>,<<"Post-Apocalyptic">>]},
{<<"language">>,<<"English">>},
{<<"license">>,
[{[{<<"url">>,<<"http://www.lulu.com">>},
{<<"id">>,<<"4880508">>}]}]},
{<<"page_count">>,276},
{<<"publisher">>,<<"Vajra Enterprises">>},
{<<"subtitle">>,<<"The Role Playing Game">>},
{<<"system">>,[<<"Organic Rule Components">>]},
{<<"title">>,<<"KidWorld">>},
{<<"last_modified">>,
<<"2011-06-08T01:58:44.697Z">>},
{<<"publisher_url">>,
<<"http://www.vajraenterprises.com/">>}]}},
[{couch_btree,lookup,3},
{couch_btree,lookup_kpnode,5},
{couch_btree,lookup,2},
{couch_db,get_full_doc_info,2},
{couch_db,open_doc_int,3},
{couch_db,open_doc,3},
{couch_httpd_db,couch_doc_open,4},
{couch_httpd_show,maybe_open_doc,2}]}}]},
{couch_httpd_show,show_etag,4},
{couch_httpd_show,handle_doc_show,6},
{couch_httpd_db,do_db_req,2},
{couch_httpd,handle_request_int,5},
{mochiweb_http,headers,5},
{proc_lib,init_p_do_apply,3}]
[Wed, 08 Jun 2011 21:29:58 GMT] [info] [<0.106.0>] 127.0.0.1 - - 'GET' /bookshelf/_design/bookshelf_loader/_show/book/4ddbbd8f3fd780c65e03c0a5e901fd57 500
I can only guess that one of the attachments is corrupt. I have tried running the tests in the Futon Test Suite that are applicable to attachments; the "attachments" test ran for over 12 hours without completing.
I would just delete the document but I don't have a revision number and any attempt to access that document fails. I tried querying the _all_docs view with cURL, but it was interrupted half-way with the error:
{"id":"40af854814db1b4890c37c669a11de28","key":"40af854814db1b4890c37c669a11de28","value":{"rev":"4-ff85b5dbb2e4800ab4bfee81f32f0866"}}
curl: (56) Received problem 2 in the chunky parser
Is there any way that I can delete the offending document (or its attachments) knowing only the _id and not the _rev?
Edit: I paged back through the log and found the initial crash, as below:
[Wed, 08 Jun 2011 02:00:05 GMT] [error] [<0.679.0>] ** Generic server <0.679.0> terminating
** Last message in was {update_docs,<0.4902.0>,
[[{doc,<<"4ddbbd8f3fd780c65e03c0a5e901fd57">>,
{4,
[<<81,240,217,60,28,224,204,196,19,243,27,
111,230,88,90,205>>,
<<171,3,185,48,48,23,182,2,95,122,48,132,
184,105,254,242>>,
<<69,27,210,166,21,121,125,192,47,237,187,
37,74,195,109,22>>,
<<145,31,9,71,4,24,15,90,97,154,177,102,
151,156,111,34>>]},
{[{<<"author">>,
[<<"Brian St. Claire-King">>,
<<"Eloy LaSanta">>]},
{<<"book_type">>,[<<"Roleplaying Game">>]},
{<<"date_added">>,[2011,6,7]},
{<<"date_published">>,2008},
{<<"genre">>,
[<<"Futuristic">>,<<"Post-Apocalyptic">>]},
{<<"language">>,<<"English">>},
{<<"license">>,
[{[{<<"url">>,<<"http://www.lulu.com">>},
{<<"id">>,<<"4880508">>}]}]},
{<<"page_count">>,276},
{<<"publisher">>,<<"Vajra Enterprises">>},
{<<"subtitle">>,
<<"The Role Playing Game">>},
{<<"system">>,
[<<"Organic Rule Components">>]},
{<<"title">>,<<"KidWorld">>},
{<<"last_modified">>,
<<"2011-06-08T01:58:44.697Z">>},
{<<"publisher_url">>,
<<"http://www.vajraenterprises.com/">>}]},
[{att,<<"KidWorld_2010_for_pdf.pdf">>,
<<"application/pdf">>,76729048,76729048,
<<143,119,231,165,202,3,166,134,23,195,
94,84,227,25,87,193>>,
4,
{<0.676.0>,[4280434790]},
identity},
{att,<<"kidworld copy.png">>,
<<"image/png">>,21674,21674,
<<237,107,129,30,82,36,136,61,29,17,202,
217,209,88,187,125>>,
3,
{<0.676.0>,[4280406118]},
identity}],
false,[]}]],
[],false,false}
** When Server state == {db,<0.678.0>,<0.679.0>,nil,<<"1307486659185780">>,
<0.676.0>,<0.680.0>,
{db_header,5,2454,0,
{4280430351,{508,9}},
{4280431672,517},
nil,0,nil,nil,1000},
2454,
{btree,<0.676.0>,
{4280430351,{508,9}},
#Fun<couch_db_updater.7.69395062>,
#Fun<couch_db_updater.8.86519079>,
#Fun<couch_btree.5.124754102>,
#Fun<couch_db_updater.9.24674233>},
{btree,<0.676.0>,
{4280431672,517},
#Fun<couch_db_updater.10.90337910>,
#Fun<couch_db_updater.11.13595824>,
#Fun<couch_btree.5.124754102>,
#Fun<couch_db_updater.12.34906778>},
{btree,<0.676.0>,nil,
#Fun<couch_btree.0.83553141>,
#Fun<couch_btree.1.30790806>,
#Fun<couch_btree.2.124754102>,nil},
2454,<<"bookshelf">>,
"../var/lib/couchdb/bookshelf.couch",[],[],nil,
{user_ctx,null,[],undefined},
nil,1000,
[before_header,after_header,on_file_open],
false}
** Reason for termination ==
** {{case_clause,{[{<<"author">>,
[<<"Brian St. Claire-King">>,<<"Eloy LaSanta">>]},
{<<"book_type">>,[<<"Roleplaying Game">>]},
{<<"date_added">>,[2011,6,7]},
{<<"date_published">>,2008},
{<<"genre">>,[<<"Futuristic">>,<<"Post-Apocalyptic">>]},
{<<"language">>,<<"English">>},
{<<"license">>,
[{[{<<"url">>,<<"http://www.lulu.com">>},
{<<"id">>,<<"4880508">>}]}]},
{<<"page_count">>,276},
{<<"publisher">>,<<"Vajra Enterprises">>},
{<<"subtitle">>,<<"The Role Playing Game">>},
{<<"system">>,[<<"Organic Rule Components">>]},
{<<"title">>,<<"KidWorld">>},
{<<"last_modified">>,<<"2011-06-08T01:58:44.697Z">>},
{<<"publisher_url">>,
<<"http://www.vajraenterprises.com/">>}]}},
[{couch_btree,modify_node,4},
{couch_btree,modify_kpnode,6},
{couch_btree,modify_node,4},
{couch_btree,query_modify,4},
{couch_btree,add_remove,3},
{couch_db_updater,update_docs_int,5},
{couch_db_updater,handle_info,2},
{gen_server,handle_msg,5}]}
[Wed, 08 Jun 2011 02:00:06 GMT] [error] [<0.679.0>] {error_report,<0.33.0>,
{<0.679.0>,crash_report,
[[{initial_call,{couch_db_updater,init,['Argument__1']}},
{pid,<0.679.0>},
{registered_name,[]},
{error_info,
{exit,
{{case_clause,
{[{<<"author">>,
[<<"Brian St. Claire-King">>,<<"Eloy LaSanta">>]},
{<<"book_type">>,[<<"Roleplaying Game">>]},
{<<"date_added">>,[2011,6,7]},
{<<"date_published">>,2008},
{<<"genre">>,[<<"Futuristic">>,<<"Post-Apocalyptic">>]},
{<<"language">>,<<"English">>},
{<<"license">>,
[{[{<<"url">>,<<"http://www.lulu.com">>},
{<<"id">>,<<"4880508">>}]}]},
{<<"page_count">>,276},
{<<"publisher">>,<<"Vajra Enterprises">>},
{<<"subtitle">>,<<"The Role Playing Game">>},
{<<"system">>,[<<"Organic Rule Components">>]},
{<<"title">>,<<"KidWorld">>},
{<<"last_modified">>,<<"2011-06-08T01:58:44.697Z">>},
{<<"publisher_url">>,
<<"http://www.vajraenterprises.com/">>}]}},
[{couch_btree,modify_node,4},
{couch_btree,modify_kpnode,6},
{couch_btree,modify_node,4},
{couch_btree,query_modify,4},
{couch_btree,add_remove,3},
{couch_db_updater,update_docs_int,5},
{couch_db_updater,handle_info,2},
{gen_server,handle_msg,5}]},
[{gen_server,terminate,6},{proc_lib,init_p_do_apply,3}]}},
{ancestors,[<0.678.0>,<0.675.0>]},
{messages,[]},
{links,[<0.678.0>]},
{dictionary,[]},
{trap_exit,true},
{status,running},
{heap_size,6765},
{stack_size,24},
{reductions,65012}],
[]]}}
This is my _show function. I don't have any _update functions.
function(doc, req) {
var ddoc = this;
var Mustache = require("lib/mustache");
var path = require("vendor/couchapp/lib/path").init(req);
var indexPath = path.list('index','recent-books',{descending:true, limit:25});
var data = {
header : {
index : indexPath,
headerTitle : "Bookshelf"
},
scripts : {
assets : path.asset()
},
index : indexPath,
pageTitle : doc ? "Edit: " + doc.title : "Create a new book",
assets : path.asset(),
db : req.path[0]
};
if (doc) {
data.doc = JSON.stringify(doc);
data.author = doc.author;
data.book_types = getCheckboxValues(bookTypeArray, doc.book_type);
data.book_type_other = getOthers(bookTypeArray, doc.book_type);
data.cover_image = getAttachmentName(doc._attachments, "image/png");
data.date_added = getDateString(doc.date_added);
data.date_published = doc.date_published;
data.edition = doc.edition;
data.issue_number = doc.issue_number;
data.file_name = getAttachmentName(doc._attachments, "application/pdf");
data.genres = getCheckboxValues(genreArray, doc.genre);
data.genre_other = getOthers(genreArray, doc.genre);
data._id = doc._id;
data.languages = getDropdownValues(languageArray, doc.language);
data.licenses = getLicenses(doc.license);
data.page_count = doc.page_count;
data.publishers = getDropdownValues(publisherArray, doc.publisher);
data.publisher_other = getOther(publisherArray, doc.publisher);
data.publisher_url = doc.publisher_url;
data._rev = doc._rev;
data.subtitle = doc.subtitle;
data.systems = getCheckboxValues(systemArray, doc.system);
data.system_other = getOthers(systemArray, doc.system);
data.tags = doc.tags;
data.title = doc.title;
data.version = doc.version;
} else {
data.doc = JSON.stringify({});
data.date_added = getDateString();
data.book_types = getCheckboxValues(bookTypeArray);
data.languages = getDropdownValues(languageArray, "English");
data.licenses = "http://www.drivethrurpg.com,";
data.publishers = getDropdownValues(publisherArray);
data.genres = getCheckboxValues(genreArray);
data.systems = getCheckboxValues(systemArray);
}
return Mustache.to_html(ddoc.templates.edit, data, ddoc.templates.partials);
}
I didn't paste all of the relatively simple helper functions (e.g. getCheckboxValues) that translate between what is stored in Couch and what goes on the screen, but I can if you think they'd be salutary.
I was finally able to access an older view and get the _rev of the document. Issuing the DELETE command with cURL results in the same "case_clause" error as before, however. Any attempt to PUT or POST a new document into the database will hang indefinitely and the database becomes inaccessible from that point on. (Fortunately I have a backup copy, which still has the one corrupt file but is at least accessible.)
As #JasonSmith mentioned in the comments above, this can sometimes be caused by a bug in your Javascript. I stumbled upon this same error message by accidentally setting a View's name property to a function directly rather than to an object containing its map and optional reduce functions. AKA
Bad:
"views": {
"test_view": "function (doc) { ... }"
}
Good:
"views": {
"test_view": {
"map": "function (doc) { ... }"
}
}

Resources