Skip to content

Commit

Permalink
reworked and optimised screenshot pick-up functions (only triggering …
Browse files Browse the repository at this point in the history
…scan on specific DN types and using sys/xattr.h to check for com.apple.metadata:kMDItemIsScreenCapture)
  • Loading branch information
rsms committed Jan 13, 2010
1 parent a31172f commit 0a7e42b
Show file tree
Hide file tree
Showing 3 changed files with 89 additions and 30 deletions.
10 changes: 10 additions & 0 deletions scrup.xcodeproj/project.pbxproj
Expand Up @@ -773,6 +773,9 @@
GCC_PREFIX_HEADER = src/prefix.pch;
GCC_PREPROCESSOR_DEFINITIONS = "DEBUG=1";
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = NO;
GCC_WARN_UNUSED_VALUE = YES;
INFOPLIST_FILE = resources/Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = Scrup;
Expand All @@ -789,11 +792,18 @@
"$(inherited)",
"\"$(SRCROOT)/frameworks\"",
);
GCC_AUTO_VECTORIZATION = YES;
GCC_ENABLE_OBJC_GC = required;
GCC_ENABLE_SSE3_EXTENSIONS = YES;
GCC_ENABLE_SSE41_EXTENSIONS = YES;
GCC_ENABLE_SSE42_EXTENSIONS = YES;
GCC_MODEL_TUNING = G5;
GCC_PRECOMPILE_PREFIX_HEADER = YES;
GCC_PREFIX_HEADER = src/prefix.pch;
GCC_VERSION = com.apple.compilers.llvm.clang.1_0;
GCC_WARN_UNUSED_FUNCTION = YES;
GCC_WARN_UNUSED_LABEL = NO;
GCC_WARN_UNUSED_VALUE = YES;
INFOPLIST_FILE = resources/Info.plist;
INSTALL_PATH = "$(HOME)/Applications";
PRODUCT_NAME = Scrup;
Expand Down
2 changes: 2 additions & 0 deletions src/DPAppDelegate.h
Expand Up @@ -75,6 +75,8 @@
-(IBAction)updateMenuItem:(id)sender;
-(IBAction)saveState:(id)sender;

-(NSArray *)sortedUploadedScreenshots; // sorted on date desc.
-(NSArray *)sortedUploadedScreenshotKeys; // keys instead of records
-(void)updateListOfRecentUploads;

-(void)startObservingDesktop;
Expand Down
107 changes: 77 additions & 30 deletions src/DPAppDelegate.m
Expand Up @@ -9,8 +9,9 @@
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/xattr.h>

#define SCREENSHOT_LOG_LIMIT 10
#define SCREENSHOT_LOG_LIMIT 10 /* todo: make configurable */

/*@interface NSStatusBar (Unofficial)
-(id)_statusItemWithLength:(float)f withPriority:(int)d;
Expand Down Expand Up @@ -168,61 +169,91 @@ -(NSDictionary *)screenshotsOnDesktop {
*/
-(NSDictionary *)screenshotsAtPath:(NSString *)dirpath modifiedAfterDate:(NSDate *)lmod {
NSFileManager *fm = [NSFileManager defaultManager];
NSDirectoryEnumerator *den = [fm enumeratorAtPath:dirpath];
NSArray *direntries;
NSMutableDictionary *files = [NSMutableDictionary dictionary];
NSString *path;
NSDate *mod;
NSError *error;
NSDictionary *attrs, *xattrs;
NSDictionary *attrs;
int fd;

for (NSString *fn in den) {
direntries = [fm contentsOfDirectoryAtPath:dirpath error:&error];
if (!direntries) {
[log error:@"%s failed to list contents of directory at '%@' -- %@", _cmd, dirpath, error];
return nil;
}

for (NSString *fn in direntries) {
//[log debug:@"%s testing %@", _cmd, fn];

// always skip dotfiles
if ([fn hasPrefix:@"."]) {
//[log debug:@"%s skipping: filename begins with a dot", _cmd];
continue;
}

// if prefix matching is used, test for it
if (!(filePrefixMatch == nil || [filePrefixMatch length] == 0 || [fn hasPrefix:filePrefixMatch])) {
[log debug:@"%s skipping: filename prefix does not match", _cmd];
continue;
}

// skip any file not ending in ".png"
// todo: add support for other kinds. I believe there's a defaults property which can be
// changed to output different file types.
if (
// if filePrefixMatch is used, test it
(!(filePrefixMatch == nil || [filePrefixMatch length] == 0 || [fn hasPrefix:filePrefixMatch]))
||
// otherwise we test for the ".png" suffix
(![fn hasSuffix:@".png"])
if (([fn length] < 10) ||
// ".png" suffix is expected
(![fn compare:@".png" options:NSCaseInsensitiveSearch range:NSMakeRange([fn length]-5, 4)] != NSOrderedSame)
)
{
//[log debug:@"%s skipping: not ending in \".png\" (case-insensitive)", _cmd];
continue;
}

// build path
path = [dirpath stringByAppendingPathComponent:fn];

// Skip any file which name does not contain a SP.
// This is a semi-ugly fix -- since we want to avoid matching the filename against
// all possible screenshot file name schemas (must be hundreds), we make the
// assumption that all language formats have this in common: it contains at least one SP.
if ([fn rangeOfString:@" "].location == NSNotFound) {
//[log debug:@"%s skipping: not containing SP", _cmd];
continue;
}

// query file attributes (rich stat)
attrs = [fm attributesOfItemAtPath:path error:&error];
if (!attrs) {
[log error:@"failed to read attributes of '%@' because: %@ -- skipping", path, error];
//[log error:@"failed to read attributes of '%@' because: %@ -- skipping", path, error];
continue;
}

// must be able to stat and must be a regular file
// must be a regular file
if ([attrs objectForKey:NSFileType] != NSFileTypeRegular) {
//[log debug:@"%s skipping: not a regular file", _cmd];
continue;
}

// check last modified date
mod = [attrs objectForKey:NSFileModificationDate];
if (lmod && (!mod || [mod compare:lmod] == NSOrderedAscending)) {
// file is too old
//[log debug:@"%s skipping: too old", _cmd];
continue;
}

// confirm xattr:com.apple.metadata:kMDItemIsScreenCapture
xattrs = [attrs objectForKey:@"NSFileExtendedAttributes"];
if (!xattrs || ![xattrs objectForKey:@"com.apple.metadata:kMDItemIsScreenCapture"]) {
// no xattrs or not a screenshot
char attrValue[512];
ssize_t attrSize = getxattr([path UTF8String], // path
"com.apple.metadata:kMDItemIsScreenCapture", // name
&attrValue, // value
512, // how much data to fetch
0, // position
XATTR_NOFOLLOW // options
);
if (attrSize == -1) {
[log debug:@"%s skipping: no xattr:com.apple.metadata:kMDItemIsScreenCapture", _cmd];
continue;
}

Expand All @@ -236,14 +267,17 @@ -(NSDictionary *)screenshotsAtPath:(NSString *)dirpath modifiedAfterDate:(NSDate

-(void)checkForScreenshotsAtPath:(NSString *)dirpath {
NSDictionary *files;
NSArray *sortedKeys;

NSArray *paths;

// find new screenshots
if (!(files = [self findUnprocessedScreenshotsOnDesktop]))
return;
sortedKeys = [files keysSortedByValueUsingComparator:^(id a, id b) {
return [b compare:a];
}];
for (NSString *path in sortedKeys) {

// sort on key (path)
paths = [files keysSortedByValueUsingComparator:^(id a, id b) { return [b compare:a]; }];

// process each file
for (NSString *path in paths) {
[self vacuumUploadedScreenshots];
[self processScreenshotAtPath:path modifiedAtDate:[files objectForKey:path]];
}
Expand Down Expand Up @@ -682,6 +716,14 @@ - (IBAction)updateMenuItem:(id)sender {
}
}

- (NSArray *)sortedUploadedScreenshotKeys {
NSMutableArray *a = [NSMutableArray arrayWithCapacity:[uploadedScreenshots count]];
for (NSDictionary *rec in [self sortedUploadedScreenshots]) {
[a addObject:[rec objectForKey:@"fn"]];
}
return a;
}

- (NSArray *)sortedUploadedScreenshots {
NSMutableArray *a = [NSMutableArray arrayWithCapacity:[uploadedScreenshots count]];
[uploadedScreenshots enumerateKeysAndObjectsWithOptions:0 usingBlock:^(id key, id obj, BOOL *stop) {
Expand Down Expand Up @@ -777,8 +819,14 @@ - (IBAction)orderFrontSettingsWindow:(id)sender {
}

-(void)onDirectoryNotification:(NSNotification *)n {
[log debug:@"received directory notification => %@ ([object class] => %@)", n, [[n object] class]];
[self checkForScreenshotsAtPath:screenshotLocation];
id obj = [n object];
[log debug:@"received directory notification => %@ ([object class] => %@)", n, obj ? [obj class] : nil];
// WARNING: Possible problem: "FNObject 469-101" is a string we have found by trial-and-error
// and is far from official or even documented, thus might differ in future OS versions etc.
// But since there are a _lot_ of directory notifications received, we need this op.
if (obj && [obj isKindOfClass:[NSString class]] && [obj isEqualToString:@"FNObject 469-101"]) {
[self checkForScreenshotsAtPath:screenshotLocation];
}
}

- (void)startObservingDesktop {
Expand Down Expand Up @@ -893,13 +941,12 @@ -(NSMutableDictionary *)uploadedScreenshotForOperation:(HTTPPOSTOperation *)op {


-(void)vacuumUploadedScreenshots {
NSFileManager *fm = [NSFileManager defaultManager];
NSFileManager *fm;
NSArray *rmkeys;

if ([uploadedScreenshots count] > SCREENSHOT_LOG_LIMIT) {
NSArray *rmkeys;
rmkeys = [[uploadedScreenshots allKeys] sortedArrayUsingComparator:^(id a, id b) {
return [b compare:a options:NSNumericSearch];
}];
fm = [NSFileManager defaultManager];
rmkeys = [self sortedUploadedScreenshotKeys];
rmkeys = [rmkeys subarrayWithRange:NSMakeRange(SCREENSHOT_LOG_LIMIT, [rmkeys count]-SCREENSHOT_LOG_LIMIT)];

// remove any thumbnails
Expand All @@ -908,7 +955,7 @@ -(void)vacuumUploadedScreenshots {
for (NSString *fn in rmkeys) {
BOOL removed = [fm removeItemAtPath:[thumbCacheDir stringByAppendingPathComponent:fn] error:nil];
if (removed)
[log debug:@"removed old screenshot thumbnail %@", fn];
[log debug:@"removed old screenshot record for '%@'", fn];
}

[uploadedScreenshots removeObjectsForKeys:rmkeys];
Expand Down

0 comments on commit 0a7e42b

Please sign in to comment.