It's the 'master' branch on GitHub:
https://www.zotero.org/support/dev/source_code#working_with_git_repositories
There's a vastly expanded test suite that you can run to make sure
things are working locally.
Note that the translation and style architectures are both still
incomplete, which might impair some of your BBT work. Web translation
works in some cases, but import/export translation might not. Probably
plenty else to work on, though. Things are pretty different, so let me
know if you have any questions.
To be safe, this branch shouldn't be synced with a production server
account yet.
On Monday, February 22, 2016 at 9:20:45 PM UTC+1, Dan Stillman wrote:
There's a vastly expanded test suite that you can run to make sure
things are working locally.
Super. How do I setup the tests?
$ ./test/runtests.sh
Mozilla Firefox 46.0.1
Possibly unhandled rejection:
Must have a source and a callback
[Exception... "Must have a source and a callback" nsresult: "0x80070057 (NS_ERROR_ILLEGAL_VALUE)" location: "JS frame :: chrome://zotero/content/xpcom/file.js :: Zotero.File</this.getContentsAsync :: line 225" data: no]
Zotero.File</this.getContentsAsync@chrome://zotero/content/xpcom/file.js:225:4
Zotero.Schema</_updateBundledFilesAtLocation<@chrome://zotero/content/xpcom/schema.js:595:19
From previous event:
Zotero.Schema</this.updateBundledFiles<@chrome://zotero/content/xpcom/schema.js:477:21
From previous event:
Zotero.Schema</this.updateSchema</</<@chrome://zotero/content/xpcom/schema.js:94:13
From previous event:
Zotero.Schema</this.updateSchema</<@chrome://zotero/content/xpcom/schema.js:91:5
From previous event:
Zotero.Schema</this.updateSchema<@chrome://zotero/content/xpcom/schema.js:89:11
From previous event:
_initFull<@chrome://zotero/content/xpcom/zotero.js:578:25
From previous event:
init/<@chrome://zotero/content/xpcom/zotero.js:436:12
self.setTimeout/<.notify@resource://zotero/bluebird.js:54:5
From previous event:
init@chrome://zotero/content/xpcom/zotero.js:195:10
ZoteroService@file:///Users/emile/zotero/zotero/components/zotero-service.js:340:4
XPCOMUtils__getFactory/factory.createInstance@resource://gre/modules/XPCOMUtils.jsm:326:19
@chrome://zotero/content/include.js:1:14
When running on latest master, I get:
$ ./test/runtests.sh
Mozilla Firefox 46.0.1
Possibly unhandled rejection:
Must have a source and a callback
[Exception... "Must have a source and a callback" nsresult: "0x80070057 (NS_ERROR_ILLEGAL_VALUE)" location: "JS frame :: chrome://zotero/content/xpcom/file.js :: Zotero.File</this.getContentsAsync :: line 225" data: no]
Mozilla Firefox 46.0.1
Possibly unhandled rejection:
xmlhttp is undefined
TypeError: xmlhttp is undefined
Zotero.Schema</this.updateFromRepository<@chrome://zotero/content/xpcom/schema.js:1062:7
From previous event:
Zotero.Schema</this.updateBundledFiles<@chrome://zotero/content/xpcom/schema.js:489:11
OK, that get's me a little further:
Mozilla Firefox 46.0.1
Possibly unhandled rejection:
xmlhttp is undefined
TypeError: xmlhttp is undefined
Zotero.Schema</this.updateFromRepository<@chrome://zotero/content/xpcom/schema.js:1062:7
Zotero.File
#getContentsAsync()
✓ should handle an empty file (486 ms)
✓ should handle an extended character (6 ms)
✓ should handle an extended Windows-1252 character (10 ms)
✓ should handle a GBK character (5 ms)
✓ should handle an invalid character (5 ms)
#copyDirectory()
✓ should copy all files within a directory (52 ms)
#zipDirectory()
✓ should compress a directory recursively (83 ms)
Zotero.Fulltext
console.error: CustomizableUI:
unable to normalize widget
#indexItems()
✖ [FAIL] "before all" hook
Error: Download failed with status 2153390067 at
Error: Download failed with status 2153390067
Zotero.File</this.download</<@chrome://zotero/content/xpcom/file.js:405:21
NetUtil_asyncFetch/<.onStopRequest@resource://gre/modules/NetUtil.jsm:128:17
#downloadPDFTool()
✖ [FAIL] should install the PDF tools
Error: Download failed with status 2153390067 at
Error: Download failed with status 2153390067
Zotero.File</this.download</<@chrome://zotero/content/xpcom/file.js:405:21
NetUtil_asyncFetch/<.onStopRequest@resource://gre/modules/NetUtil.jsm:128:17
#getUnsyncedContent()
✖ [FAIL] "before all" hook
HTTP GET https://www.zotero.org/download/xpdf/latest.json failed with status code 0 at
HTTP GET https://www.zotero.org/download/xpdf/latest.json failed with status code 0
(No stack trace)
I'm still wholly freaked out by the promise-and-yield stuff. Is there any tutorial that could ease me into this? It's a little much to take in all at once.
739/745 tests passed
Possibly unhandled rejection:
this._potentialTranslators is undefined
TypeError: this._potentialTranslators is undefined
Zotero.Translate.Base.prototype.complete@chrome://zotero/content/xpcom/translation/translate.js:1397:1
Zotero.Translate.Web.prototype.complete@chrome://zotero/content/xpcom/translation/translate.js:2025:20
Zotero.Translate.Base.prototype.getTranslators/<@chrome://zotero/content/xpcom/translation/translate.js:1171:4
self.setTimeout/<.notify@resource://zotero/bluebird.js:54:5
From previous event:
Zotero.Translate.Base.prototype.getTranslators@chrome://zotero/content/xpcom/translation/translate.js:1115:10
Zotero_Browser.Tab.prototype.detectTranslators<@chrome://zotero/content/browser.js:825:3
From previous event:
Zotero_Browser</contentLoad@chrome://zotero/content/browser.js:358:3
That gets me further still. I still get errors in the sync part of the test (but perhaps that's to be expected)
Zotero.Fulltextconsole.error: CustomizableUI:
unable to normalize widget
#indexItems()
✖ [FAIL] "before all" hook
Error: Download failed with status 2153390067 at
Error: Download failed with status 2153390067
Zotero.File</this.download</<@chrome://zotero/content/xpcom/file.js:405:21
NetUtil_asyncFetch/<.onStopRequest@resource://gre/modules/NetUtil.jsm:128:17
I'm still wholly freaked out by the promise-and-yield stuff. Is there any tutorial that could ease me into this? It's a little much to take in all at once.
--
You received this message because you are subscribed to a topic in the Google Groups "zotero-dev" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/zotero-dev/naAxXIbpDhU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to zotero-dev+...@googlegroups.com.
To post to this group, send email to zoter...@googlegroups.com.
Visit this group at https://groups.google.com/group/zotero-dev.
For more options, visit https://groups.google.com/d/optout.
No idea why my traffic would be intercepted at work -- as I'm connected through a non-managed laptop, they can't be doing it on my laptop, so it'd need to be a transparent proxy of sorts... but I work in the IT dept, and no one around me knows how this could be the case. I have the network boys on it, but they said that even if it were to be the case, I should also have seen warnings from Chrome in usual browsing.
You received this message because you are subscribed to the Google Groups "zotero-dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email to zotero-dev+...@googlegroups.com.
It could be security software as well. The test creates a new Firefox profile, so if something has installed a custom root certificate in your current browsers, this would fail but those wouldn't.
1. Can I add my own tests, and can I select which tests to run (I'd rather not run the whole Zotero test suite for my own unit tests), and
I'm looking at Zotero.Utilities.Internal.itemToExportFormat, but I don't see where the async happens there. itemToExportFormat uses item.getField, which must be async (right?) but it looks like it's called directly rather than using a yield. What am I missing here?
On 6/13/16 3:39 AM, Emiliano Heyns wrote:
1. Can I add my own tests, and can I select which tests to run (I'd rather not run the whole Zotero test suite for my own unit tests), and
Well, you can add files to test/tests/ locally. If you add fooTest.js, you can then run tests in that file with `./test/runtests.sh foo` (or multiple separated by commas), or `./test/runtests.sh -g 'test title to search for' foo` to run specific tests.
I'm looking at Zotero.Utilities.Internal.itemToExportFormat, but I don't see where the async happens there. itemToExportFormat uses item.getField, which must be async (right?) but it looks like it's called directly rather than using a yield. What am I missing here?
It's not async — the `return {Promise<Object>}` comment is just outdated. `item.getField()` isn't async — item data is loaded either for a whole library at a time (automatically when a library is viewed for the first time in the UI) or when Zotero.Items.getAsync() is used (for rare cases where an item might be accessed before that happens). Either way, once there's a Zotero.Item, all item data is available.
On Mon, Jun 13, 2016 at 10:02 AM, Dan Stillman <dsti...@zotero.org> wrote:
On 6/13/16 3:39 AM, Emiliano Heyns wrote:
1. Can I add my own tests, and can I select which tests to run (I'd rather not run the whole Zotero test suite for my own unit tests), and
Well, you can add files to test/tests/ locally. If you add fooTest.js, you can then run tests in that file with `./test/runtests.sh foo` (or multiple separated by commas), or `./test/runtests.sh -g 'test title to search for' foo` to run specific tests.
But this would require me running the tests from a Zotero checkout, right?
I'm looking at Zotero.Utilities.Internal.itemToExportFormat, but I don't see where the async happens there. itemToExportFormat uses item.getField, which must be async (right?) but it looks like it's called directly rather than using a yield. What am I missing here?
It's not async — the `return {Promise<Object>}` comment is just outdated. `item.getField()` isn't async — item data is loaded either for a whole library at a time (automatically when a library is viewed for the first time in the UI) or when Zotero.Items.getAsync() is used (for rare cases where an item might be accessed before that happens). Either way, once there's a Zotero.Item, all item data is available.
This is different from the 4.0 behavior, right? IIRC, 4.0 would load an item in stages, depending on what fields were requested through getField.
Does this mean that if I overlay zoteroPane.xul and init from there, I can be sure the whole library is already in memory?
Any particular reason to keep the getField for access? If the item is already in memory, wouldn't something like item.extra be preferable to item.getField('extra'), avoiding the function call overhead?
On 6/13/16 4:22 AM, Emiliano Heyns wrote:
But this would require me running the tests from a Zotero checkout, right?
I mean, sure. Not sure what else you're asking, though, since this is about the Zotero test suite.
I'm looking at Zotero.Utilities.Internal.itemToExportFormat, but I don't see where the async happens there. itemToExportFormat uses item.getField, which must be async (right?) but it looks like it's called directly rather than using a yield. What am I missing here?
It's not async — the `return {Promise<Object>}` comment is just outdated. `item.getField()` isn't async — item data is loaded either for a whole library at a time (automatically when a library is viewed for the first time in the UI) or when Zotero.Items.getAsync() is used (for rare cases where an item might be accessed before that happens). Either way, once there's a Zotero.Item, all item data is available.
This is different from the 4.0 behavior, right? IIRC, 4.0 would load an item in stages, depending on what fields were requested through getField.
Yes. Details explained in Promisification [1] and Deasyncification [2].
But I misspoke slightly. When the library has been loaded in the UI, items can be retrieved with Zotero.Items.get() and will have all data loaded, as explained in Deasyncification.
If the library hasn't been loaded, either the load will need to be triggered [3] or the item will need to be retrieved with `Zotero.Items.getAsync()` and then the relevant data types will need to be loaded on demand (e.g., item.loadItemData()), as was the original plan in Promsification.
BBT really only reacts to 3 things:
- A right-click on an item
- A change on an item or the addition of an item to a collection
- Exports
From the looks of it, any item involved in any of these will have been loaded. Correct? That would simplify a few matters.
Zotero.Promise.coroutine(function*() {
// this is never reached.
yield store.queryAsync('CREATE TABLE IF NOT EXISTS lokijs (name PRIMARY KEY, data)');
return (yield store.queryAsync('INSERT OR REPLACE INTO lokijs (name, data) VALUES (?, ?)', [name, serialized]));
}).then(function() {
return callback();
}, function(reason) {
return callback(new Error(reason));
})["catch"](function(reason) {
return callback(new Error(reason));
});
Zotero.Promise.coroutine(function*() {
var err;
try {
(yield store.queryAsync('CREATE TABLE IF NOT EXISTS lokijs (name PRIMARY KEY, data)'));
(yield store.queryAsync('INSERT OR REPLACE INTO lokijs (name, data) VALUES (?, ?)', [name, serialized]));
callback();
} catch (err) {
callback(err);
}
})();
I now have this piece of code, which is called from a shutdown listener called from Zotero.addShutdownListener:
Zotero.Promise.coroutine(function*() {
var err;
try {
(yield store.queryAsync('CREATE TABLE IF NOT EXISTS lokijs (name PRIMARY KEY, data)'));
(yield store.queryAsync('INSERT OR REPLACE INTO lokijs (name, data) VALUES (?, ?)', [name, serialized]));
callback();
} catch (err) {
callback(err);
}
})();
but that gets me
FATAL ERROR: AsyncShutdown timeout in profile-before-change Conditions: [{"name":"Sqlite.jsm shutdown blocker","state":{"description":"Waiting for connections to close","state":[{"name":"betterbibtex-lokijs.sqlite#0: waiting for shutdown","state":{"identifier":"betterbibtex-lokijs.sqlite#0","isCloseRequested":false,"hasDbConn":true,"hasInProgressTransaction":false,"pendingStatements":0,"statementCounter":8},"filename":"resource://gre/modules/Sqlite.jsm","lineNumber":259,"stack":["resource://gre/modules/Sqlite.jsm:ConnectionData:259","resource://gre/modules/Sqlite.jsm:OpenedConnection:1122","resource://gre/modules/Sqlite.jsm:openConnection/</<:933"]}]},"filename":"resource://gre/modules/Sqlite.jsm","lineNumber":157,"stack":["resource://gre/modules/Sqlite.jsm:null:157","resource://gre/modules/XPCOMUtils.jsm:XPCU_defineLazyGetter/<.get:198","resource://gre/modules/Sqlite.jsm:ConnectionData:259","resource://gre/modules/Sqlite.jsm:OpenedConnection:1122","resource://gre/modules/Sqlite.jsm:openConnection/</<:933"]}] At least one completion condition failed to complete within a reasonable amount of time. Causing a crash to ensure that we do not leave the user with an unresponsive process draining resources.
followed by the Mozilla crash reporter popping up.
On 7/3/16 4:53 PM, Emiliano Heyns wrote:You can't safely run async code in a shutdown listener, because Zotero.shutdown() doesn't wait for promises from listeners. If it did, things would probably work as expected when switching from full mode to connector mode (which is going away anyway), because Zotero.shutdown() would wait to close the DB until your queries had finished, but there's a good chance it would cause problems at application shutdown, since Zotero.shutdown() is triggered by an observer for the XPCOM quit-application event, and the XPCOM observer system doesn't wait for promises.
Also:
1) It's still not clear that you're using Zotero.Promise.coroutine() correctly. It should almost never be necessary to use the Z.P.coroutine(function* { ...})() syntax. Even if the shutdown code waited for promises from listeners, you'd want to simply wrap the shutdown listener with Z.P.coroutine().
From your code above, unless you didn't copy a 'yield' from the coroutine() line or the callback is resolving a promise that's returned below (which would be an awkward way of doing things), your shutdown listener isn't actually waiting for the generator function to complete before returning.
2) You don't need parentheses around 'yield …' when it's the only thing on the line, only when it's used as a parameter.
You can yield on Zotero.Translators.init(), which is a no-op if they're
already loaded.
Error(s) encountered during statement execution: no such column: fileName [QUERY: SELECT fileName, metadataJSON, lastModifiedTime FROM translatorCache
The error doesn't have a stack trace, so I don't know where it originates exactly, but I'm assuming it's from calling Zotero.Translators.init(). Should I be waiting/can I wait for Zotero to have started up and "settled" before initializing BBT? I have a sense a lot of the issues I'm going to hit in porting BBT will be prevented by waiting for that.
On another topic, a lot of the things I'm going to be stumbling into are bound to be issues arising from me monkey-patching Zotero left and right. This is going to be harder (and some would say this is a good thing) with the pervasive use of coroutines, which usually wraps anonymous functions, safely out of reach of BBT.
Rather than trying to find new ways to monkey-patch stuff under 5.x, I'd much prefer it if there could be clean APIs (or fixes in some occasions) to make these monkey-patches obsolete; could you have a look at my list of monkey-patches below to see if any of them would qualify? Trigger warning: absolutely everything listed below is an ugly hack, bound to offend the sensibilities of most sensible software engineers. I don't do these for the mere fun of it, and try to avoid monkey-patching if any other route is available. Some of these may in fact already have APIs that have popped up after I implemented these hacks -- I must acknowledge I haven't checked whether this is the case.
On Wednesday, July 6, 2016 at 7:29:50 PM UTC+2, Dan Stillman wrote:
You can yield on Zotero.Translators.init(), which is a no-op if they're
already loaded.
That gets me a little further, but I'm now getting
Error(s) encountered during statement execution: no such column: fileName [QUERY: SELECT fileName, metadataJSON, lastModifiedTime FROM translatorCache
The error doesn't have a stack trace, so I don't know where it originates exactly, but I'm assuming it's from calling Zotero.Translators.init(). Should I be waiting/can I wait for Zotero to have started up and "settled" before initializing BBT? I have a sense a lot of the issues I'm going to hit in porting BBT will be prevented by waiting for that.
Rather than trying to find new ways to monkey-patch stuff under 5.x, I'd much prefer it if there could be clean APIs (or fixes in some occasions) to make these monkey-patches obsolete; could you have a look at my list of monkey-patches below to see if any of them would qualify?
Yeah, you shouldn't do anything before Zotero.initializationPromise resolves. You can add a 'yield Zotero.initializationPromise;' to your startup code.
You can also yield on Zotero.schemaUpdatePromise to wait for the built-in translators to be updated from the repo, which happens about a second after initialization.
Rather than trying to find new ways to monkey-patch stuff under 5.x, I'd much prefer it if there could be clean APIs (or fixes in some occasions) to make these monkey-patches obsolete; could you have a look at my list of monkey-patches below to see if any of them would qualify?
Will get back to you on these.
The error doesn't have a stack trace, so I don't know where it originates exactly, but I'm assuming it's from calling Zotero.Translators.init(). Should I be waiting/can I wait for Zotero to have started up and "settled" before initializing BBT? I have a sense a lot of the issues I'm going to hit in porting BBT will be prevented by waiting for that.
Yeah, you shouldn't do anything before Zotero.initializationPromise resolves. You can add a 'yield Zotero.initializationPromise;' to your startup code.
On Friday, July 8, 2016 at 8:11:26 AM UTC+2, Dan Stillman wrote:Yeah, you shouldn't do anything before Zotero.initializationPromise resolves. You can add a 'yield Zotero.initializationPromise;' to your startup code.
You can also yield on Zotero.schemaUpdatePromise to wait for the built-in translators to be updated from the repo, which happens about a second after initialization.That sounds good.I am used to initializing BBT from the overlay loader code. This code does not run in the context of a generator function, so I don't think calling "yield" there will do the job. You previously mentioned I shouldn't do something likeZotero.Promise.coroutine(function* () {yield Zotero.schemaUpdatePromise();// do my init stuff here
})()
Rather than trying to find new ways to monkey-patch stuff under 5.x, I'd much prefer it if there could be clean APIs (or fixes in some occasions) to make these monkey-patches obsolete; could you have a look at my list of monkey-patches below to see if any of them would qualify? Trigger warning: absolutely everything listed below is an ugly hack, bound to offend the sensibilities of most sensible software engineers. I don't do these for the mere fun of it, and try to avoid monkey-patching if any other route is available. Some of these may in fact already have APIs that have popped up after I implemented these hacks -- I must acknowledge I haven't checked whether this is the case.
- Zotero.Sync.Storage.processDownload. I wrap this method, calling it without changes, but add a timeout a second after it fires to emulate an observer on 'item' firing. It currently calls my own notifier handler function directly, but could in principle just activate any registered item-change handler. I do this because the notification for webdav-synced attachments fires before the attachment is unpacked from its zip, this delayed notification will allow me to handle the updated attachment as usual.
- Zotero.Server.DataListener.prototype._generateResponse / Zotero.Server.DataListener.prototype._requestFinished. Before anyone freaks out: I'm not proposing to keep anything even resembling what I did, but the reason I wrapped this is so I could return promises for strings rather than simply strings using the sendResponseCallback. This would seem be a good philosophical fit for 5.0 to replace the callback with a promise-based approach. My current wrapper looks whether the response is a promise, and acts on that if so, and passes on all arguments unchanged if the response is a string
- Zotero.Search.prototype.save. I wrap this method to be notified of changes to a search in order to schedule auto-exports of saved searches.
- Zotero.ItemTreeView.prototype.getCellText. This is one I'd love to get rid off. Currently, it looks if the cell being requested is for the 'extra' column, and if the relevant setting is enabled in BBT, it returns the citation key instead of the text that would normally be looked up. If both these conditions do not apply, I simply call the original method. This is for sure a rather ugly hack, but Zotero has loads of places where it validates that a "valid" column is being shown, and of course "citekey" is not one of the approved fields for any time, so this is the only thing I could think of. I'd very much prefer there to be a clean way for extensions to declare a new column type.
- Zotero.Translate.Export.prototype.translate. Another one I'd love to ditch. I wrap this method to get more info available on the translation that's about to start. I note what's about to be exported (saved search, collection, library or "other") so I can register that for auto-export if that option has been enabled for the current export. I note the folder being exported to (although this is really just for a minor BBT feature, I could endure its loss)
- Zotero.Translate.Export.prototype._prepareTranslation. Marks this._itemGetter to note whether file export has been requested. This is only used in nextItem (see following point)
- Zotero.Translate.ItemGetter.prototype.nextItem. The reason for wrapping this method may have become obsolete in 5.0. In 4.0, pre-translation reference serialization took the bulk of the time of the translation -- according to my highly unscientific tests, some 80% of the export time was spent in serializing references, I'd venture to guess because it involved SQL calls, which have high per-call overhead. This was especially problematic for auto-exports. What this wrapper does is cache the serialization result to memory and directly return that if a hit was found. This was only done under two conditions: the running export was for a BBT translator, and no file export had been requested. For any other case, the original method is called unchanged. The associated cache was invalidated when a reference changed, yada yada. Given that 5.0 appears to have most of the references in memory in any case, this may no longer be relevant, but at least in 4.0 it is a real performance drag on exports -- all translators would benefit from this cache, but I didn't dare to expose non-BBT translators to it because I wasn't sure about side effects.
- ZoteroPane_Local.buildCollectionContextMenu. Wrapped in order to add menu entries to the collection right-click menu.
Any news on these? I'd have to do some really ugly stuff to get this done in 5.0, and I'd really rather not. In 4.0, monkey-patching is simple and can be done fairly safely when diligent, but in 5.0 (or specifically, in any bluebird-heavy code), it's really hard to do targeted monkey-patches and I'd end up doing one mega-monkey-patch and trying to derive from context what actual behavior to patch... it's not pretty, and the code makes me very nervous.
Zotero.Sync.Storage.processDownload. I wrap this method, calling it without changes, but add a timeout a second after it fires to emulate an observer on 'item' firing. It currently calls my own notifier handler function directly, but could in principle just activate any registered item-change handler. I do this because the notification for webdav-synced attachments fires before the attachment is unpacked from its zip, this delayed notification will allow me to handle the updated attachment as usual.
Zotero.Search.prototype.save. I wrap this method to be notified of changes to a search in order to schedule auto-exports of saved searches.
On 8/7/16 5:56 AM, Emiliano Heyns wrote:
Any news on these? I'd have to do some really ugly stuff to get this done in 5.0, and I'd really rather not. In 4.0, monkey-patching is simple and can be done fairly safely when diligent, but in 5.0 (or specifically, in any bluebird-heavy code), it's really hard to do targeted monkey-patches and I'd end up doing one mega-monkey-patch and trying to derive from context what actual behavior to patch... it's not pretty, and the code makes me very nervous.
Not that I'm a fan of monkey-patching, but can you explain the difficulty of monkey-patching in 5.0?
I think there are some more general ways we could greatly improve extensibility, but realistically I don't see that happening until we're on Electron/React.
But a couple easy ones (I think) from your list:
Zotero.Sync.Storage.processDownload. I wrap this method, calling it without changes, but add a timeout a second after it fires to emulate an observer on 'item' firing. It currently calls my own notifier handler function directly, but could in principle just activate any registered item-change handler. I do this because the notification for webdav-synced attachments fires before the attachment is unpacked from its zip, this delayed notification will allow me to handle the updated attachment as usual.
Not sure if this is still a problem in 5.0, but I'd think you could do this with a normal notifier observer and just delay if it's an attachment in a WebDAV library (Zotero.Sync.Storage.Local.getModeForLibrary()).
Zotero.Search.prototype.save. I wrap this method to be notified of changes to a search in order to schedule auto-exports of saved searches.
You should be able to use 'search' notifier events for this.
The difficulty of monkey-patching in 5.0 is not with named functions but with anonymous functions (if you grep for "then.*function" you find some of them). This is a common pattern with promises as far as I can tell, but patching them, while not impossible, is incredibly fragile.
I think there are some more general ways we could greatly improve extensibility, but realistically I don't see that happening until we're on Electron/React.
Ah, pity. Is there a timeline for this? As in, could I sit out 5.0?
Zotero.Sync.Storage.processDownload. I wrap this method, calling it without changes, but add a timeout a second after it fires to emulate an observer on 'item' firing. It currently calls my own notifier handler function directly, but could in principle just activate any registered item-change handler. I do this because the notification for webdav-synced attachments fires before the attachment is unpacked from its zip, this delayed notification will allow me to handle the updated attachment as usual.
Not sure if this is still a problem in 5.0, but I'd think you could do this with a normal notifier observer and just delay if it's an attachment in a WebDAV library (Zotero.Sync.Storage.Local.getModeForLibrary()).
Ah, I didn't know this. But this was actually one I had hoped might just be changed in Zotero. If the item notifier is supposed to fire after an attachment change, shouldn't it also fire after the attachment was unpacked from the zip? It does after all change (again) at that point.
On 8/11/16 7:39 AM, Emiliano Heyns wrote:
The difficulty of monkey-patching in 5.0 is not with named functions but with anonymous functions (if you grep for "then.*function" you find some of them). This is a common pattern with promises as far as I can tell, but patching them, while not impossible, is incredibly fragile.
Could you give specific examples where you want to do this? coroutines()/generators are mostly an alternative (wrapper, technically) to the .then callback model of promises, so it's mostly either in code that hasn't been updated to use generators or functioning as a glorified goto to avoid some large ifs. Either way, many existing instances could probably be rewritten. But I would also think that in most places just using the value of the final promise would be sufficient.
Not sure if this is still a problem in 5.0, but I'd think you could do this with a normal notifier observer and just delay if it's an attachment in a WebDAV library (Zotero.Sync.Storage.Local.getModeForLibrary()).
Ah, I didn't know this. But this was actually one I had hoped might just be changed in Zotero. If the item notifier is supposed to fire after an attachment change, shouldn't it also fire after the attachment was unpacked from the zip? It does after all change (again) at that point.
If you can provide example code that demonstrates the problem, I can take a look.
simply wrapped coroutine around each method and called it using yield (tried but kept losing track of where I had to modify the caller)
simply wrapped coroutine around each method and called it using yield (tried but kept losing track of where I had to modify the caller)Yeah, that is the basic premise. An IDE is helpful in identifying all callers of a function. So there was
On Wednesday, October 5, 2016 at 9:05:31 PM UTC+2, Adomas Venčkauskas wrote:simply wrapped coroutine around each method and called it using yield (tried but kept losing track of where I had to modify the caller)
Yeah, that is the basic premise. An IDE is helpful in identifying all callers of a function. So there was
I don't know of an IDE that does coffeescript refactoring. Maybe it's time to for me to give up coffeescript.
My code relies crucially on certain parts having executed in full before anything else happens. I am hoping that the mechanism that's used for the schemaupdatepromise will allow me to emulate that. It looks like that promise is created once, but it's then called/used/executed (I don't know exactly which of these apply here) at multiple places, and it becomes a no-op once its done its work (I think?), but I don't grasp how it does all this.
My code relies crucially on certain parts having executed in full before anything else happens. I am hoping that the mechanism that's used for the schemaupdatepromise will allow me to emulate that. It looks like that promise is created once, but it's then called/used/executed (I don't know exactly which of these apply here) at multiple places, and it becomes a no-op once its done its work (I think?), but I don't grasp how it does all this.
Yes, basically. You can await the result of a promise from multiple places, and as soon as the promise is resolved (or if it's already resolved) all such places will receive the same resolution value or rejection. (Behind the scenes, these are just additional callbacks, and if the promise is resolved the callbacks get called immediately.) This isn't really a standard use of promises (and some people would probably argue it's an anti-pattern), but it works well enough for our purposes.
Yes, basically. You can await the result of a promise from multiple places, and as soon as the promise is resolved (or if it's already resolved) all such places will receive the same resolution value or rejection. (Behind the scenes, these are just additional callbacks, and if the promise is resolved the callbacks get called immediately.) This isn't really a standard use of promises (and some people would probably argue it's an anti-pattern), but it works well enough for our purposes.So this is just standard promises behavior, but an atypical use? Resolving a promise somehow signals all its callers in any case?
--
You received this message because you are subscribed to a topic in the Google Groups "zotero-dev" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/zotero-dev/naAxXIbpDhU/unsubscribe.
To unsubscribe from this group and all its topics, send an email to zotero-dev+unsubscribe@googlegroups.com.
To post to this group, send email to zoter...@googlegroups.com.
Visit this group at https://groups.google.com/group/zotero-dev.
For more options, visit https://groups.google.com/d/optout.
Can you explain how Zotero.Schema.schemaUpdatePromise works, technically? It looks like it enables any number of places to wait on the promise. I could use something like this to do dependent loads in BBT, e.g. I'd want parts of the BBT code to only proceed after I've loaded the BBT database.
--
On 6/13/16 3:39 AM, Emiliano Heyns wrote:
1. Can I add my own tests, and can I select which tests to run (I'd rather not run the whole Zotero test suite for my own unit tests), and
Well, you can add files to test/tests/ locally. If you add fooTest.js, you can then run tests in that file with `./test/runtests.sh foo` (or multiple separated by commas), or `./test/runtests.sh -g 'test title to search for' foo` to run specific tests.
If you're not testing something translator-related, it's much quicker to pass -b to skip bundled file installation. -d 5 will display debug output. See ./test/runtests.sh -h for more.
I'm looking at Zotero.Utilities.Internal.itemToExportFormat, but I don't see where the async happens there. itemToExportFormat uses item.getField, which must be async (right?) but it looks like it's called directly rather than using a yield. What am I missing here?
It's not async — the `return {Promise<Object>}` comment is just outdated. `item.getField()` isn't async — item data is loaded either for a whole library at a time (automatically when a library is viewed for the first time in the UI) or when Zotero.Items.getAsync() is used (for rare cases where an item might be accessed before that happens). Either way, once there's a Zotero.Item, all item data is available.
(It's easy enough to confirm this — if the return value doesn't have a .then, it's not a promise.)
On 3/2/17 3:30 PM, Emiliano Heyns wrote:
> How about a significant number of tags but each assigned to only one
> or two references? I'd love to get rid of saving the citekey in the
> extra field, and I've been toying with having it as a tag instead.
The Extra field is really the right place for this — that's what
everyone is using for pseudo-fields that will eventually make their way
into the official data model.
> I see there are several types of tags, what distinguishes these types?
Current tag types are 0 (manual) and 1 (automatic). There may be other
types in the future (e.g., private/workflow tags), but 0 and 1 are the
only types that are currently allowed and the only ones that can sync.
On Thursday, March 2, 2017 at 9:36:24 PM UTC+1, Dan Stillman wrote:On 3/2/17 3:30 PM, Emiliano Heyns wrote:
> How about a significant number of tags but each assigned to only one
> or two references? I'd love to get rid of saving the citekey in the
> extra field, and I've been toying with having it as a tag instead.
The Extra field is really the right place for this — that's what
everyone is using for pseudo-fields that will eventually make their way
into the official data model.
I'm one of those everyones, but I was hoping to get away from it.
Is there a way to save an item that doesn't trigger observers (or at least excludes specific ones)? I react to changes to items, some of these changes may cause the citation key to change, which I'd then save in the extra field, which cause triggers the notifiers, etc. Right now I check whether the citekey generated after the notification is unchanged and don't save the item in that case, but if there's a way to not have to do that, that'd be great. I'm in the process of cleaning up BBT so as to minimize the porting work.
The Extra field is really the right place for this — that's what
everyone is using for pseudo-fields that will eventually make their way
into the official data model.
I'm one of those everyones, but I was hoping to get away from it.
I don't see why — it's by far the simplest solution until we have a better one.
Is there a way to save an item that doesn't trigger observers (or at least excludes specific ones)? I react to changes to items, some of these changes may cause the citation key to change, which I'd then save in the extra field, which cause triggers the notifiers, etc. Right now I check whether the citekey generated after the notification is unchanged and don't save the item in that case, but if there's a way to not have to do that, that'd be great. I'm in the process of cleaning up BBT so as to minimize the porting work.
You can skip various actions [1], but if you modify visible, syncing data then most of those need to trigger.
I'm not totally clear what you're trying to avoid, though.
I'm not totally clear what you're trying to avoid, though.
I'm trying to prevent a save-loop.
I don't see how a save loop and skipping notifier events are related. You have to watch for notifier events that happen after normal saves in order to know if you need to update the citation key. You then need to save the item again to update the citation key. So that's two saves regardless.
The extra notifier events that happen from your save just allow the rest of Zotero to react to your changes. You shouldn't skip those.
You could obviously monkey-patch things to set the citation key during the initial save (not that I'd recommend that), but that wouldn't have anything to do with the notifier events.
We could possibly add a pre-save hook to data objects that allowed changes to be made to the data being saved, but we'd have to carefully consider the implications of that.
On Thu, Mar 2, 2017 at 10:31 PM, Dan Stillman <dsti...@zotero.org> wrote:
I don't see how a save loop and skipping notifier events are related. You have to watch for notifier events that happen after normal saves in order to know if you need to update the citation key. You then need to save the item again to update the citation key. So that's two saves regardless.
I'm not worried about those two, my worry is about the notification from the second, which will cause me to re-calculate the citation key, and to re-save it, and...
I currently do recalculate and just don't save when it's unchanged, but I'm actively looking for places where I can drop code, and certainly where it involves touching the DB.
We could possibly add a pre-save hook to data objects that allowed changes to be made to the data being saved, but we'd have to carefully consider the implications of that.
That would be very helpful indeed. The citation key is in many cases a derived property.
do you need to access the DB to generate a citation key?
But I recently did add a way to pass along arbitrary notifier data with a save:
yield item.saveTx({
notifierData: {
bbtCitekeyUpdate: true
}
)
Then in the notify(event, type, ids, extraData), check for extraData[id].bbtCiteKeyUpdate and ignore if present.
(This hasn't been tested extensively, so let me know if you run into trouble.)
Also, I forget the specifics, but there should be info in extraData[id] for a 'modify' that tells you which fields changed, which hopefully you're using to avoid running the regeneration unless relevant fields have changed. (This probably also needs more test coverage.)
var updateCitationKey = function (env, obj) {
obj.setField('extra', generateExtra(obj));
};
Zotero.Items.onBeforeSave.addListener(updateCitationKey);
(Or, ideally, some documented visibility into the internal obj._changed properties so you could see what fields changed without regenerating the whole thing.)
One problem with this is that, if multiple observers were registered, the order would be undefined, and you couldn't really guarantee that you were seeing the final data being saved. So in that way just using notifier observers after the save makes more sense.
Also, I forget the specifics, but there should be info in extraData[id] for a 'modify' that tells you which fields changed, which hopefully you're using to avoid running the regeneration unless relevant fields have changed. (This probably also needs more test coverage.)
Zotero.Notifier.registerObserver(this, ['item'])
action :"modify", type :"item", ids :[2], extraData":{"2":{}}
{"action":"modify","type":"item","ids":[2],"extraData":{"2":{}}}
On 2/18/17 3:26 PM, Emiliano Heyns wrote:
On Wednesday, October 5, 2016 at 11:25:54 AM UTC+2, Emiliano Heyns wrote:Can you explain how Zotero.Schema.schemaUpdatePromise works, technically? It looks like it enables any number of places to wait on the promise. I could use something like this to do dependent loads in BBT, e.g. I'd want parts of the BBT code to only proceed after I've loaded the BBT database.
What is the state of the database after schemaUpdatePromise resolves? I know some things are in memory after startup (like collections) but are (parts of) the references also in memory after this resolves?
Collections and searches from all libraries should be loaded, and items from the currently selected library should be loaded. Items from other libraries won't be loaded. (Specifically, in Standalone, schema updating waits for Zotero.uiReadyPromise, which is resolved once items for the current library are loaded.)
If you need items that might not be loaded, you can use Zotero.Items.getAsync() (or getByLibraryAndKeyAsync) to get them. When you know that they're loaded, the synchronous versions are better.
Better in what way? And how can I tell whether they're loaded?
Better in that synchronous calls are faster.
You don't need to test for whether the items are loaded — you just either use get() or getAsync() depending on whether the objects might be unloaded, based on what I said above. So, e.g., in zotero:// protocol handler stuff we use getAsync(), because a zotero:// URL could be loaded externally for an item in a library hasn't been opened yet in the Zotero UI.