Setting properties with ScriptingBridge

Staring to use ScriptingBridge in Swift to enable faster scripting access to DT3 (and avoid having to use AppleScript as a conduit to call a Swift command line utility and deal with its results).

The plan is to be able to read the plaintext of a record (no problem) and change the record name based on its contents. But I can’t seem to write a name, instead getting an error “Cannot assign to property: ‘xxx’ is immutable”.

Any guidance how to get around this?

Getting/setting properties via the scripting bridge is probably not much faster than AppleScript as the main bottlenecks are AppleEvents and process switching.

The relevant parts of your code would be useful.

import ScriptingBridge

@objc protocol DEVONthinkApplication {
   @objc optional var selection: [AnyObject] { get }
   @objc optional var name: String { get }
   @objc optional var version: String { get }
}

@objc protocol DEVONthinkRecord {
   @objc optional var name: String { get set}
   @objc optional var plainText: String { get }
}

extension SBApplication: DEVONthinkApplication {}
extension SBObject: DEVONthinkRecord {}

class DT3controller: DEVONthinkApplication, DEVONthinkRecord {
   let devonThink: DEVONthinkApplication?
   
   init() {
      devonThink = (SBApplication(bundleIdentifier: "com.devon-technologies.think3"))
   }
   
   func getSelectedObjectName() -> String? {
      
      guard devonThink != nil
      else {
         print("DEVONthink Pro is not running.")
         return nil
      }
      
      print("up and running: " + devonThink!.name! + " " + devonThink!.version!)
      
      guard var selection = devonThink!.selection, var firstItem = selection.first as? DEVONthinkRecord
      else {
         print("No selection.")
         return nil
      }

      for x in selection {
         print( (x as? DEVONthinkRecord)?.name ?? "" )
      }
 
       
      firstItem.name = "asdf".  <<<<< "Cannot assign to property: 'firstItem' is immutable"
      return firstItem.name
   }


}

I’m trying to grasp how this is faster or more reliable than, selecting some text and choosing Edit > Set Name As, choosing the same command from the contextual menu, or pressing Control-Command-I. :thinking:

Because I’m renaming thousands of files.

Based on what criterion? You logically can’t select text in thousands of documents and run the script.

Dude, I’m not, the entire textual analysis module is. But that’s not the problem. The problem is changing DT3 properties directly through the ScriptingBridge. Any insight?

Undoubtedly. However, simplicity of the code base and using a single language throughout is a desirable design goal even if performance doesn’t improve.

Right now I am executing this process via an AppleScript conduit that calls a Swift script and processes its returned text to rename and tag files in DT3. This works but is cumbersome and difficult to debug. An integrated approach is preferable.

All I want to do is replicate what AppleScript already does: read properties of a DT3 record (can do this) and write to others (can’t do this because of the 'immutable" error).

I may be a bit dense here. But if you simply want to rename the currently selected files, why not use JXA or AppleScript directly?
Or is Swift giving you something in this context that the other languages don’t?

See OP: “The plan is to be able to read the plaintext of a record (no problem) and change the record name based on its contents.”

Right now I am executing this process via an AppleScript conduit that calls a Swift script that performs textual analysis on the record’s plaintext, generates a set of keywords, structures a new filename and tag set, and returns that data as text to enable AppleScript to rename and tag files in DT3.

This requires
a) writing code in AppleScript, debugging and maintaining it, and calling it via a DT3 hook.
b) using that script to call a Swift script which requires writing code in Swift, debugging and maintaining its different code base, and having to move data back and forth between the two programs.

So: two languages, two IDEs, two separate code bases.

This works but is cumbersome and difficult to debug. An integrated approach is preferable.

Guys: I’m not asking for an analysis of my problem set. I’m asking how to set properties in DT3 using ScriptingBridge.

I don’t “simply want to rename files”. I want to set properties in DT3 using ScriptingBridge. This code is an example of the problem I am having. It isn’t my entire application.

You’re out in your own waters. There is no specific support for ScriptingBridge re: Swift nor is anyone in here (developmentally or the forums apparently) is using it.

@cgrunenberg is the only person that may have any insight on the underlying technical side of things.

Never used scripting bridge personally but for DEVONthink it makes no difference whether an AppleEvent was sent via AppleScript, JXA or scripting bridge, it’s only important that it receives the event which doesn’t seem to be the case here.

Well, I’ve never used Scripting Bridge. And as usual, Apple’s documentation is obtuse. The only thing I noticed its that setTo seems to be used to set properties,

Are you using one of Apple’s frameworks to analyze your text? If so, is it only accessible with Swift? Otherwise, using ASObjC might be an alternative.

Scripting bridge in Swift doesn’t seem to be popular and is kind of broken, see e.g.

No such thing. The ScriptingBridge still goes through the osascript apple event interface. The Swift stage is just an additional traffic re-route.

As @chrillek suggest, more efficient to use the osascript interface from JXA.

Just to close the circle on this for future enquirers…

ScriptingBridge works just fine in Mac 14.5/Xcode 15.4/Swift 5.
However, properties can’t be set in DT3 using a “normal” setter, ie., DEVONthinkRecord.name = “asdf”. Instead you must add a method to the protocol like “@objc optional func setName (_: String)”, and then call it with something like “DEVONthinkRecord.setName?(newName!)”. After that everything works as expected.

BTW using ScriptingBridge runs about 500% faster than AppleScript calls in Swift, and about 1000% faster than a comparable AppleScript script.

1 Like

The source of this script would be interesting. In addition, scripts executed in the Script Editor.app are usually slower due to the debugging & logging overhead.

Well, source code for both Applescript and swift contains large amounts of proprietary information, so I can’t really circulate it. However, they are largely conceptually structured the same way:

In applescript, several arrays of structures contain regular expressions and associated keywords. A function iterates through the arrays, sends the regular expressions out to the shell to evaluate, and based on the responses builds a result array of appropriate keywords. That result array is then used to construct a new file name and rename the file in DEVONthink.

In swift, the process is somewhat similar, though the associations between regular expressions and keywords are done with enums, and the regular expression parsing is done with the swift standard library.

Obviously, Applescript bears a huge overhead, both in talking to DEVONthink through Apple events, and in sending regular expressions to the shell for execution. Overall execution speed allowed for two renames per second, an unbearable burden for hundreds or thousands of files.

Swift on the other hand talks directly to DEVONthink through the scripting bridge, builds an array of all the selected files, pulls out their texts, and then runs the various regular expressions against that text. Finally, it builds an array of file names and tags to assign to each file and sends those back through the scripting bridge to DEVONthink. Early testing shows dozens of files are renamed in a couple of seconds; I haven’t tried a larger selection set yet.

The textual analysis process isn’t particularly exotic. However, I know the choice of swift as the language probably is. I’m trying to standardize on a single language as much as possible in our small office to avoid the overhead of having to be multilingual in SQL, shell scripts, python, swift, applescript, etc., etc.

It’s interesting to discover that bypassing Applescript and going directly to compiled code yields such a large efficiency, so much so that I am a little surprised that DEVONthink hasn’t incorporated an API to bypass Applescript altogether.

Isn’t that as “direct” as AS or JXA do these things, too?

While I understand the goal, replacing SQL by any of the other languages seems fairly challenging – if you have a database that understands SQL, how are you going to talk to it with Swift, avoiding SQL in the process?

Well… you wrote an AppleScript that calls a shell command to do something. And then you implemented everything this script does in Swift. It’s not particularly surprising, I think, that in this scenario the second approach is faster, as it avoids all the overhead of building and running shell commands, as well as interpreting their output afterward. Personally, I’d rewrite the AS code as a JXA script without using any external shell commands (since JXA as very good regex support) and compare that to the Swift implementation. Probably not much of a difference there.

I can’t speak for the DT developers, of course. But if it were me – why would I do that? I’d have to write an additional API, besides the JXA and AS ones that are already there (and that come nearly for free, as they are based on Apple’s scripting architecture). All that for a tiny number of people – there are exactly six posts mentioning “ScriptingBridge” in the forum. Since at least 12 years.

2 Likes