Welcome!
This is the community forum for my apps Pythonista and Editorial.
For individual support questions, you can also send an email. If you have a very short question or just want to say hello — I'm @olemoritz on Twitter.
Callback on file save
-
Is there or could there be a callback for custom processing whenever a file is saved in Pythonista?
-
@mikael I would love to see more things like this in Pythonista. You might be able to do something like this in
objc_util
using something called method swizzling.Basically, as I understand it, this would involve taking the existing method that's called on save and copying and renaming it somehow. Then, you would make a new method that first called the existing (renamed and copied) save function, and then called a new function.
You would then overwrite the existing method with that new one, which would save and do something else. Since you copied the function, saving will still happen after overwriting the function.
I'm pretty sure @ProfSpaceCadet has done this in the past.
As you can probably tell, this is really unstable, and probably won't work in many cases. You might also crash the app a lot with this approach. But it may work.
+1 for adding this into the app. That'd be great.
-
@Webmaster4o Is "swizzling" in Objective-C what you would call "monkey-patching" in Python and other dynamic lanugages? Basically something like this:
old_print = print def print(*args, **kwargs): old_print("Hi!", *args, **kwargs)
Actually this is kind of the wrong way to monkey-patch a method, because someone else might have modified
print
already and kept the unmodifiedprint
asold_print
. To avoid this kind of name conflict you can use a closure:def _make_new_print(): old_print = print def new_print(*args, **kwargs): old_print("Hi!", *args, **kwargs) return new_print print = _make_new_print() del _make_new_print
-
@dgelessus Yes, this is what I'm talking about. This might be possible for Pythonista's internal objective C methods.
-
Here is a very raw saveData swizzle... This probably needs to check for the existence of an existing swizzle and undo it first, otherwise you can lose the original!
# coding: utf-8 # coding: utf-8 import editor from objc_util import * from objc_util import parse_types import ctypes import inspect t=editor._get_editor_tab() def saveData(_self,_sel): print 'hi' #call original method obj=ObjCInstance(_self) orig=getattr(obj,'_original'+c.sel_getName(_sel)) orig() def swizzle(cls, old_sel, new_fcn): '''swizzles cls.old_sel with new_fcn. Assumes encoding is the same. if class already has swizzledSelector, unswizzle first. original selector can be called via originalSelectir ''' orig_method=c.class_getInstanceMethod(cls.ptr, sel(old_sel)) #new_method=c.class_getInstanceMethod(cls, sel(new_sel)) type_encoding=str(cls.instanceMethodSignatureForSelector_(sel(old_sel))._typeString()) parsed_types = parse_types(str(type_encoding)) restype = parsed_types[0] argtypes = parsed_types[1] # Check if the number of arguments derived from the selector matches the actual function: argspec = inspect.getargspec(new_fcn) if len(argspec.args) != len(argtypes): raise ValueError('%s has %i arguments (expected %i)' % (method, len(argspec.args), len(argtypes))) IMPTYPE = ctypes.CFUNCTYPE(restype, *argtypes) imp = IMPTYPE(new_fcn) retain_global(imp) new_sel='_original'+old_sel didAdd=c.class_addMethod(cls.ptr, sel(new_sel), imp, type_encoding) new_method=c.class_getInstanceMethod(cls.ptr, sel(new_sel)) # swap imps c.method_exchangeImplementations.restype=None c.method_exchangeImplementations.argtypes=[c_void_p,c_void_p] c.method_exchangeImplementations(orig_method, new_method) return new_sel t=editor._get_editor_tab() cls=ObjCInstance(c.object_getClass(t.ptr)) swizzle(cls,'saveData',saveData)
-
Thanks guys! Maybe just a little bit hack-y than I was hoping for, but why not?
The idea here was to flag the file "dirty" with a timestamp, and then with a background thread write the file to a cloud store when activity settles down for a moment. I would use reminders as a store.
On other devices I would still use a manual "update latest" user action instead of swizzling the load function, to avoid confusion and since reminders seem to require opening the Reminders app before they are synced.
-
I've updated this with a more robust and generalized swizzling method, which swizzles saveData in this particular case.
There is also a OMFileWatcher object which seems like it is doing something like what you want ... but I have been unable to get its delegate methods fired off.
Also, I have not yet tried this, but it appears fnctl might work on iOS, so would be a way to get callbacks on file modify without relying on swizzling.
Another approach would be to simply have a Timer thread which checks the list of files you are watching. If you are going to have a thread anyway, probably only slightly more effort to check file dates than to be triggered by a callback.