-
-
Notifications
You must be signed in to change notification settings - Fork 170
Fix syncthing plugin #223
Fix syncthing plugin #223
Conversation
|
I reverted my changes on lines 58-64. I'd changed them while experimenting with why settings was broken and forgot to revert. As an aside, as far as I can tell the option does nothing, but I added it back just to avoid changing anything I didn't need to. |
|
@ngencokamin lets continue the discussion here. what have you changed to make the config widget work? |
Hey sorry for the slow reply, been crazy busy with work. Lemme run through and break down my changes and why I made them/what they do |
ngencokamin
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here's a lil breakdown of the code choices I made! Sorry it took so long 😭
| class Plugin(PluginInstance, GlobalQueryHandler): | ||
|
|
||
| config_key = 'syncthing_api_key' | ||
| config_key = 'api_key' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is to make the config key consistent with references throughout the rest of the script
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looking at the code, it seems that self.st is only defined when api key is set. So for anyone using this plugin and having that config present (maybe from a past version), this would be a breaking change. If I'm understanding this correctly, the plugin is not working properly for someone enabling it for the first time.
I would recommend keeping the old config key name not to force people into having to input it again.
| if self._api_key is None: | ||
| self._api_key = '' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This fixes the widget not appearing with error 12:26:44 [crit:albert.python] Unable to cast Python instance of type <class 'NoneType'> to C++ type 'QString'
Basically just initializes api_key to an empty string if an api key isn't found in the config. Otherwise it breaks because of it being None/null
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could be shortened to
self._api_key = self.readConfig(self.config_key, str) or ""
syncthing/__init__.py
Outdated
| @property | ||
| def api_key(self) -> str: | ||
| return self._api_key | ||
| # return '1234' |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I didn't mean to leave this in and will make a commit removing it
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Okay, this is fixed now
| try: | ||
| if self.st: | ||
| config = self.st.system.config() | ||
|
|
||
| devices = dict() | ||
| for d in config['devices']: | ||
| if not d['name']: | ||
| d['name'] = d['deviceID'] | ||
| d['_shared_folders'] = {} | ||
| devices[d['deviceID']] = d | ||
|
|
||
| folders = dict() | ||
| for f in config['folders']: | ||
| if not f['label']: | ||
| f['label'] = f['id'] | ||
| for d in f['devices']: | ||
| devices[d['deviceID']]['_shared_folders'][f['id']] = f | ||
| folders[f['id']] = f | ||
|
|
||
| matcher = Matcher(query.string) | ||
|
|
||
| # create device items | ||
| for device_id, d in devices.items(): | ||
| device_name = d['name'] | ||
|
|
||
| if match := matcher.match(device_name): | ||
| device_folders = ", ".join([f['label'] for f in d['_shared_folders'].values()]) | ||
|
|
||
| actions = [] | ||
| if d['paused']: | ||
| actions.append( | ||
| Action("resume", "Resume synchronization", | ||
| lambda did=device_id: self.st.system.resume(did)) | ||
| ) | ||
| else: | ||
| actions.append( | ||
| Action("pause", "Pause synchronization", | ||
| lambda did=device_id: self.st.system.pause(did)) | ||
| ) | ||
|
|
||
| item = StandardItem( | ||
| id=device_id, | ||
| text=f"{device_name}", | ||
| subtext=f"{'Paused ' if d['paused'] else ''}Syncthing device. " | ||
| f"Shared: {device_folders if device_folders else 'Nothing'}.", | ||
| iconUrls=self.iconUrls, | ||
| actions=actions | ||
| ) | ||
|
|
||
| results.append(RankItem(item, match)) | ||
|
|
||
| # create folder items | ||
| for folder_id, f in folders.items(): | ||
| folder_name = f['label'] | ||
| if match := matcher.match(folder_name): | ||
| folders_devices = ", ".join([devices[d['deviceID']]['name'] for d in f['devices']]) | ||
| item = StandardItem( | ||
| id=folder_id, | ||
| text=folder_name, | ||
| subtext=f"Syncthing folder {f['path']}. " | ||
| f"Shared with {folders_devices if folders_devices else 'nobody'}.", | ||
| iconUrls=self.iconUrls, | ||
| actions=[ | ||
| Action("scan", "Scan the folder", | ||
| lambda fid=folder_id: self.st.database.scan(fid)), | ||
| Action("open", "Open this folder in file browser", | ||
| lambda p=f['path']: openFile(p)) | ||
| ] | ||
| ) | ||
| results.append(RankItem(item, match)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This bit try is meant to catch it when syncthing fails to initialize due to missing or invalid api key. Otherwise it spits errors with every character entered into the launcher. Plus, having it blank with no explanation outside of the console is not the most friendly design.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I maybe could do the try except only around the bit that checks if st is initialized/valid? So that it's a little less general
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would suggest a few points to refactor this and make it more robust
First: I would look into api_key setter. It only sets self.st if the API key changes, but that includes if api key is empty. I'm not that familiar with Syncthing to know if this is allowed, but I assume you need an API key.
So instead of creating self.st every time, I would set it to None if api key is empty.
Second: You wouldn't need exception handling for empty API key. Instead, the first thing in handleGlobalQuery could be a check to self.st (that is now properly set due to the First point) and return the StandardItem with information about missing API key, something along the lines of
if not self.st:
return [StandardItem(...)]
Third: You should keep the try block for any call to syncthing, do not wrap it only for the initial call. It could go down between the loop iterations and you need to catch those errors. However, printing out "Invalid API key" may not be appropriate, as Syncthing could just be down/not available for any reason. To make it more readable, exporting this logic into another function and then calling that function in a try except block is also an option
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm crazy busy for the next few weeks, but I will mark this as draft for now and refactor when I am able. I'll let y'all know when it's ready for re-review
| if self._api_key == '': | ||
| item = StandardItem( | ||
| id=device_id, | ||
| text=f"{device_name}", | ||
| subtext=f"{'Paused ' if d['paused'] else ''}Syncthing device. " | ||
| f"Shared: {device_folders if device_folders else 'Nothing'}.", | ||
| iconUrls=self.iconUrls, | ||
| actions=actions | ||
| id="no_key", | ||
| text="Please enter your Syncthing API key in settings", | ||
| subtext="You can find your api key on your Syncthing web dashboard.", | ||
| iconUrls=self.iconUrls | ||
| ) | ||
|
|
||
| results.append(RankItem(item, match)) | ||
|
|
||
| # create folder items | ||
| for folder_id, f in folders.items(): | ||
| folder_name = f['label'] | ||
| if match := matcher.match(folder_name): | ||
| folders_devices = ", ".join([devices[d['deviceID']]['name'] for d in f['devices']]) | ||
| else: | ||
| item = StandardItem( | ||
| id=folder_id, | ||
| text=folder_name, | ||
| subtext=f"Syncthing folder {f['path']}. " | ||
| f"Shared with {folders_devices if folders_devices else 'nobody'}.", | ||
| iconUrls=self.iconUrls, | ||
| actions=[ | ||
| Action("scan", "Scan the folder", | ||
| lambda fid=folder_id: self.st.database.scan(fid)), | ||
| Action("open", "Open this folder in file browser", | ||
| lambda p=f['path']: openFile(p)) | ||
| ] | ||
| id="invalid_key", | ||
| text='Invalid API Key', | ||
| subtext=f"API Key {self._api_key} is invalid. Please try entering your API key again in settings.", | ||
| iconUrls=self.iconUrls | ||
| ) | ||
| results.append(RankItem(item, match)) | ||
|
|
||
| results.append(RankItem(item, 0)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
So in theory, this except should only fire if the api key is invalid or missing. But I can see how we might want the logic to be a little less broad. Basically rn it just checks if the key is blank, at which point it reports missing, else it reports invalid.
Oh I can also add better code comments if that'd be helpful |
|
Okay. I canged a lot 7b0c532. The dependencies uses are dead packages and the API we need is small so I decided to handcraft the syncthing API wrapper. IIUC you wanted to not silently fail but instead show an error item. IMHO this is perfectly fine for the global query handler. We dont want to have items in a global query just because any of the plugins failed in some way. However in a triggered handler this makes sense. Thats why I added def handleTriggerQuery(self, query):
try:
super().handleTriggerQuery(query)
except Exception as e:
query.add(StandardItem(id="err", text="Error", subtext=str(e), iconUrls=self.icon_urls_active))Do you have any further objections? ty @Sharsie for looking into this as well. |
|
Closing this since the original issue should be solved. Let me know if there is anything else to do. |


Syncthing wasn't working properly. Was throwing errors about st not being defined any time the user types in albert, and the error
[crit:albert.python] Unable to cast Python instance of type <class 'NoneType'> to C++ type 'QString'when opening settings, displaying a blank window instead of the api key field. This PR:Screenshots

Valid api key
Invalid api key
No api key