1
0
mirror of https://github.com/arsenetar/dupeguru-cocoa.git synced 2026-01-22 14:41:40 +00:00

Convert cocoalib submodule to be in place.

This commit is contained in:
2020-12-29 19:46:14 -06:00
parent ce0bb606b2
commit 99b98db93a
81 changed files with 5258 additions and 4 deletions

View File

@@ -0,0 +1,34 @@
#import <Cocoa/Cocoa.h>
@interface CocoaProxy : NSObject
{
NSAutoreleasePool *currentPool;
}
- (void)openPath:(NSString *)path;
- (void)openURL:(NSString *)url;
- (void)revealPath:(NSString *)path;
- (NSString *)getUTI:(NSString *)path;
- (BOOL)type:(NSString *)type conformsToType:(NSString *)refType;
- (NSString *)getAppdataPath;
- (NSString *)getCachePath;
- (NSString *)getResourcePath;
- (NSString *)systemLang;
- (NSString *)systemShortDateFormat;
- (NSString *)systemNumberDecimalSeparator;
- (NSString *)systemNumberGroupingSeparator;
- (NSString *)systemCurrency;
- (NSString *)bundleIdentifier;
- (NSString *)appVersion;
- (NSString *)osxVersion;
- (NSString *)bundleInfo:(NSString *)key;
- (void)postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo;
- (id)prefValue:(NSString *)prefname;
- (void)setPrefValue:(NSString *)prefname value:(id)value;
- (id)prefValue:(NSString *)prefname inDomain:(NSString *)domain;
- (NSString *)url2path:(NSString *)url;
- (void)createPool;
- (void)destroyPool;
- (void)reportCrash:(NSString *)crashReport withGithubUrl:(NSString *)githubUrl;
- (void)log:(NSString *)s;
- (NSDictionary *)readExifData:(NSString *)imagePath;
@end

171
cocoalib/cocoa/CocoaProxy.m Normal file
View File

@@ -0,0 +1,171 @@
#import "CocoaProxy.h"
#import "HSErrorReportWindow.h"
@implementation CocoaProxy
- (void)openPath:(NSString *)path
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL fileURLWithPath:path isDirectory:NO]];
}
- (void)openURL:(NSString *)url
{
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:url]];
}
- (void)revealPath:(NSString *)path
{
[[NSWorkspace sharedWorkspace] selectFile:path inFileViewerRootedAtPath:@""];
}
- (NSString *)getUTI:(NSString *)path
{
NSError *error;
return [[NSWorkspace sharedWorkspace] typeOfFile:path error:&error];
}
- (BOOL)type:(NSString *)type conformsToType:(NSString *)refType
{
return [[NSWorkspace sharedWorkspace] type:type conformsToType:refType];
}
- (NSString *)getAppdataPath
{
return [NSSearchPathForDirectoriesInDomains(NSApplicationSupportDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
- (NSString *)getCachePath
{
return [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) objectAtIndex:0];
}
- (NSString *)getResourcePath
{
return [[[NSBundle mainBundle] resourceURL] path];
}
- (NSString *)systemLang
{
return [[NSBundle preferredLocalizationsFromArray:[[NSBundle mainBundle] localizations]] objectAtIndex:0];
}
- (NSString *)systemShortDateFormat
{
[NSDateFormatter setDefaultFormatterBehavior:NSDateFormatterBehavior10_4];
NSDateFormatter *f = [[NSDateFormatter alloc] init];
[f setDateStyle:NSDateFormatterShortStyle];
[f setTimeStyle:NSDateFormatterNoStyle];
NSString *result = [[f dateFormat] retain];
[f release];
return [result autorelease];
}
- (NSString *)systemNumberDecimalSeparator
{
[NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSString *result = [[f decimalSeparator] retain];
[f release];
return [result autorelease];
}
- (NSString *)systemNumberGroupingSeparator
{
[NSNumberFormatter setDefaultFormatterBehavior:NSNumberFormatterBehavior10_4];
NSNumberFormatter *f = [[NSNumberFormatter alloc] init];
NSString *result = [[f groupingSeparator] retain];
[f release];
return [result autorelease];
}
- (NSString *)systemCurrency
{
return [[NSLocale currentLocale] objectForKey:NSLocaleCurrencyCode];
}
- (NSString *)bundleIdentifier
{
return [[NSBundle mainBundle] bundleIdentifier];
}
- (NSString *)appVersion
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
}
- (NSString *)bundleInfo:(NSString *)key
{
return [[NSBundle mainBundle] objectForInfoDictionaryKey:key];
}
- (NSString *)osxVersion
{
return [[NSProcessInfo processInfo] operatingSystemVersionString];
}
- (void)postNotification:(NSString *)name userInfo:(NSDictionary *)userInfo
{
[[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:userInfo];
}
- (id)prefValue:(NSString *)prefname
{
return [[NSUserDefaults standardUserDefaults] objectForKey:prefname];
}
- (void)setPrefValue:(NSString *)prefname value:(id)value
{
[[NSUserDefaults standardUserDefaults] setObject:value forKey:prefname];
}
- (id)prefValue:(NSString *)prefname inDomain:(NSString *)domain
{
NSDictionary *dict = [[NSUserDefaults standardUserDefaults] persistentDomainForName:domain];
return [dict objectForKey:prefname];
}
// Changes a file:/// path into a normal path
- (NSString *)url2path:(NSString *)url
{
NSURL *u = [NSURL URLWithString:url];
return [u path];
}
// Create a pool for use into a separate thread.
- (void)createPool
{
[self destroyPool];
currentPool = [[NSAutoreleasePool alloc] init];
}
- (void)destroyPool
{
if (currentPool != nil) {
[currentPool release];
currentPool = nil;
}
}
- (void)reportCrash:(NSString *)crashReport withGithubUrl:(NSString *)githubUrl
{
return [HSErrorReportWindow showErrorReportWithContent:crashReport githubUrl:githubUrl];
}
- (void)log:(NSString *)s
{
NSLog(@"%@", s);
}
- (NSDictionary *)readExifData:(NSString *)imagePath
{
NSDictionary *result = nil;
NSURL* url = [NSURL fileURLWithPath:imagePath];
CGImageSourceRef source = CGImageSourceCreateWithURL((CFURLRef)url, nil);
if (source != nil) {
CFDictionaryRef metadataRef = CGImageSourceCopyPropertiesAtIndex (source, 0, nil);
if (metadataRef != nil) {
result = [NSDictionary dictionaryWithDictionary:(NSDictionary *)metadataRef];
CFRelease(metadataRef);
}
CFRelease(source);
}
return result;
}
@end

118
cocoalib/cocoa/__init__.py Normal file
View File

@@ -0,0 +1,118 @@
# Created By: Virgil Dupras
# Created On: 2007-10-06
# Copyright 2015 Hardcoded Software (http://www.hardcoded.net)
# This software is licensed under the "GPLv3" License as described in the "LICENSE" file,
# which should be included with this package. The terms are also available at
# http://www.gnu.org/licenses/gpl-3.0.html
import logging
import time
import traceback
import sys
from .CocoaProxy import CocoaProxy
proxy = CocoaProxy()
def autoreleasepool(func):
def wrapper(*args, **kwargs):
proxy.createPool()
try:
func(*args, **kwargs)
finally:
proxy.destroyPool()
return wrapper
def as_fetch(as_list, as_type, step_size=1000):
"""When fetching items from a very big list through applescript, the connection with the app
will timeout. This function is to circumvent that. 'as_type' is the type of the items in the
list (found in appscript.k). If we don't pass it to the 'each' arg of 'count()', it doesn't work.
applescript is rather stupid..."""
result = []
# no timeout. default timeout is 60 secs, and it is reached for libs > 30k songs
item_count = as_list.count(each=as_type, timeout=0)
steps = item_count // step_size
if item_count % step_size:
steps += 1
logging.info('Fetching %d items in %d steps' % (item_count, steps))
# Don't forget that the indexes are 1-based and that the upper limit is included
for step in range(steps):
begin = step * step_size + 1
end = min(item_count, begin + step_size - 1)
if end > begin:
result += as_list[begin:end](timeout=0)
else: # When there is only one item, the stupid fuck gives it directly instead of putting it in a list.
result.append(as_list[begin:end](timeout=0))
time.sleep(.1)
logging.info('%d items fetched' % len(result))
return result
def extract_tb_noline(tb):
# Same as traceback.extract_tb(), but without line fetching
limit = 100
list = []
n = 0
while tb is not None and (limit is None or n < limit):
f = tb.tb_frame
lineno = tb.tb_lineno
co = f.f_code
filename = co.co_filename
name = co.co_name
list.append((filename, lineno, name, None))
tb = tb.tb_next
n = n+1
return list
def safe_format_exception(type, value, tb):
"""Format exception from type, value and tb and fallback if there's a problem.
In some cases in threaded exceptions under Cocoa, I get tracebacks targeting pyc files instead
of py files, which results in traceback.format_exception() trying to print lines from pyc files
and then crashing when trying to interpret that binary data as utf-8. We want a fallback in
these cases.
"""
try:
return traceback.format_exception(type, value, tb)
except Exception:
result = ['Traceback (most recent call last):\n']
result.extend(traceback.format_list(extract_tb_noline(tb)))
result.extend(traceback.format_exception_only(type, value))
return result
def install_exception_hook(github_url):
def report_crash(type, value, tb):
app_identifier = proxy.bundleIdentifier()
app_version = proxy.appVersion()
osx_version = proxy.osxVersion()
s = "Application Identifier: {}\n".format(app_identifier)
s += "Application Version: {}\n".format(app_version)
s += "Mac OS X Version: {}\n\n".format(osx_version)
s += ''.join(safe_format_exception(type, value, tb))
if LOG_BUFFER:
s += '\nRelevant Console logs:\n\n'
s += '\n'.join(LOG_BUFFER)
proxy.reportCrash_withGithubUrl_(s, github_url)
sys.excepthook = report_crash
# A global log buffer to use for error reports
LOG_BUFFER = []
class CocoaHandler(logging.Handler):
def emit(self, record):
msg = record.getMessage()
proxy.log_(msg)
LOG_BUFFER.append(msg)
del LOG_BUFFER[:-20]
def install_cocoa_logger():
logging.getLogger().addHandler(CocoaHandler())
def patch_threaded_job_performer():
# _async_run, under cocoa, has to be run within an autorelease pool to prevent leaks.
# You only need this patch is you use one of CocoaProxy's function (which allocate objc
# structures) inside a threaded job.
from hscommon.jobprogress.performer import ThreadedJobPerformer
ThreadedJobPerformer._async_run = autoreleasepool(ThreadedJobPerformer._async_run)

300
cocoalib/cocoa/inter.py Normal file
View File

@@ -0,0 +1,300 @@
import logging
from objp.util import pyref, dontwrap
from . import proxy
class GUIObjectView:
def refresh(self): pass
class PyGUIObject:
def __init__(self, model: pyref):
self.model = model
self.callback = None
# This *has* to be called right after initialization.
def bindCallback_(self, callback: pyref):
self.callback = callback
self.model.view = self
# Call this before the ObjC callback is deallocated to avoid calls to that deallocated instance.
def free(self):
self.model.view = None
self.callback = None
def modelRef(self) -> pyref:
return self.model
#--- Python -> Cocoa
@dontwrap
def refresh(self):
self.callback.refresh()
class PyTextField(PyGUIObject):
def text(self) -> str:
return self.model.text
def setText_(self, newtext: str):
self.model.text = newtext
class SelectableListView(GUIObjectView):
def updateSelection(self): pass
class PySelectableList(PyGUIObject):
def items(self) -> list:
# Should normally always return strings
return self.model[:]
def selectIndex_(self, index: int):
self.model.select(index)
def selectedIndex(self) -> int:
result = self.model.selected_index
if result is None:
result = -1
return result
def selectedIndexes(self) -> list:
return self.model.selected_indexes
def selectIndexes_(self, indexes: list):
self.model.select(indexes)
def searchByPrefix_(self, prefix: str) -> int:
return self.model.search_by_prefix(prefix)
#--- model --> view
@dontwrap
def update_selection(self):
self.callback.updateSelection()
class ColumnsView:
def restoreColumns(self): pass
def setColumn_visible_(self, colname: str, visible: bool): pass
class PyColumns(PyGUIObject):
def columnNamesInOrder(self) -> list:
return self.model.colnames
def columnDisplay_(self, colname: str) -> str:
return self.model.column_display(colname)
def columnIsVisible_(self, colname: str) -> bool:
return self.model.column_is_visible(colname)
def columnWidth_(self, colname: str) -> int:
return self.model.column_width(colname)
def moveColumn_toIndex_(self, colname: str, index: int):
self.model.move_column(colname, index)
def resizeColumn_toWidth_(self, colname: str, newwidth: int):
self.model.resize_column(colname, newwidth)
def setColumn_defaultWidth_(self, colname: str, width: int):
self.model.set_default_width(colname, width)
def menuItems(self) -> list:
return self.model.menu_items()
def toggleMenuItem_(self, index: int) -> bool:
return self.model.toggle_menu_item(index)
def resetToDefaults(self):
self.model.reset_to_defaults()
#--- Python --> Cocoa
@dontwrap
def restore_columns(self):
self.callback.restoreColumns()
@dontwrap
def set_column_visible(self, colname: str, visible):
self.callback.setColumn_visible_(colname, visible)
class OutlineView(GUIObjectView):
def startEditing(self): pass
def stopEditing(self): pass
def updateSelection(self): pass
class PyOutline(PyGUIObject):
def cancelEdits(self):
self.model.cancel_edits()
def canEditProperty_atPath_(self, property: str, path: list) -> bool:
node = self.model.get_node(path)
assert node is self.model.selected_node
return getattr(node, 'can_edit_' + property, False)
def saveEdits(self):
self.model.save_edits()
def selectedPath(self) -> list:
return self.model.selected_path
def setSelectedPath_(self, path: list):
self.model.selected_path = path
def selectedPaths(self) -> list:
return self.model.selected_paths
def setSelectedPaths_(self, paths: list):
self.model.selected_paths = paths
def property_valueAtPath_(self, property: str, path: list) -> object:
try:
return getattr(self.model.get_node(path), property)
except IndexError:
logging.warning("%r doesn't have a node at path %r", self.model, path)
return ''
def setProperty_value_atPath_(self, property: str, value: object, path: list):
setattr(self.model.get_node(path), property, value)
#--- Python -> Cocoa
@dontwrap
def start_editing(self):
self.callback.startEditing()
@dontwrap
def stop_editing(self):
self.callback.stopEditing()
@dontwrap
def update_selection(self):
self.callback.updateSelection()
class TableView(GUIObjectView):
def showSelectedRow(self): pass
def startEditing(self): pass
def stopEditing(self): pass
def updateSelection(self): pass
class PyTable(PyGUIObject):
#--- Helpers
@dontwrap
def _getrow(self, row):
try:
return self.model[row]
except IndexError:
msg = "Trying to get an out of bounds row ({} / {}) on table {}"
logging.warning(msg.format(row, len(self.model), self.model.__class__.__name__))
#--- Cocoa --> Python
def columns(self) -> pyref:
return self.model.columns
def add(self):
self.model.add()
def cancelEdits(self):
self.model.cancel_edits()
def canEditColumn_atRow_(self, column: str, row: int) -> object:
return self.model.can_edit_cell(column, row)
def deleteSelectedRows(self):
self.model.delete()
def numberOfRows(self) -> int:
return len(self.model)
def saveEdits(self):
self.model.save_edits()
def selectRows_(self, rows: list):
self.model.select(list(rows))
def selectedRows(self) -> list:
return self.model.selected_indexes
def selectionAsCSV(self) -> str:
return self.model.selection_as_csv()
def setValue_forColumn_row_(self, value: object, column: str, row: int):
# this try except is important for the case while a row is in edition mode and the delete
# button is clicked.
try:
self._getrow(row).set_cell_value(column, value)
except AttributeError:
msg = "Trying to set an attribute that can't: {} with value {} at row {} on table {}"
logging.warning(msg.format(column, value, row, self.model.__class__.__name__))
raise
def sortByColumn_desc_(self, column: str, desc: bool):
self.model.sort_by(column, desc=desc)
def valueForColumn_row_(self, column: str, row: int) -> object:
return self._getrow(row).get_cell_value(column)
#--- Python -> Cocoa
@dontwrap
def show_selected_row(self):
self.callback.showSelectedRow()
@dontwrap
def start_editing(self):
self.callback.startEditing()
@dontwrap
def stop_editing(self):
self.callback.stopEditing()
@dontwrap
def update_selection(self):
self.callback.updateSelection()
class ProgressWindowView(GUIObjectView):
def setProgress_(self, progress: int): pass
def showWindow(self): pass
def closeWindow(self): pass
class PyProgressWindow(PyGUIObject):
def jobdescTextField(self) -> pyref:
return self.model.jobdesc_textfield
def progressdescTextField(self) -> pyref:
return self.model.progressdesc_textfield
def pulse(self):
self.model.pulse()
def cancel(self):
self.model.cancel()
#--- Python -> Cocoa
@dontwrap
def set_progress(self, last_progress):
self.callback.setProgress_(last_progress)
@dontwrap
def show(self):
self.callback.showWindow()
@dontwrap
def close(self):
self.callback.closeWindow()
class BaseAppView:
def showMessage_(self, msg: str): pass
class PyBaseApp(PyGUIObject):
def appName(self) -> str:
return self.model.PROMPT_NAME
def appLongName(self) -> str:
return self.model.NAME
#--- Python --> Cocoa
@dontwrap
def get_default(self, key_name):
return proxy.prefValue_(key_name)
@dontwrap
def set_default(self, key_name, value):
proxy.setPrefValue_value_(key_name, value)
@dontwrap
def show_message(self, msg):
self.callback.showMessage_(msg)