├── .gitignore ├── DMHY.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ └── contents.xcworkspacedata └── xcuserdata │ └── yaqinking.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── DMHY.xcworkspace ├── contents.xcworkspacedata ├── xcshareddata │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── yaqinking.xcuserdatad │ └── UserInterfaceState.xcuserstate ├── DMHY ├── AppDelegate.h ├── AppDelegate.m ├── Assets.xcassets │ ├── AppIcon.appiconset │ │ ├── Contents.json │ │ ├── icon_128x128.png │ │ ├── icon_128x128@2x.png │ │ ├── icon_16x16.png │ │ ├── icon_16x16@2x.png │ │ ├── icon_256x256.png │ │ ├── icon_256x256@2x.png │ │ ├── icon_32x32.png │ │ ├── icon_32x32@2x.png │ │ ├── icon_512x512.png │ │ └── icon_512x512@2x.png │ └── Contents.json ├── Base.lproj │ └── Main.storyboard ├── Controller │ ├── AddKeywordViewController.h │ ├── AddKeywordViewController.m │ ├── ExportKeywordViewController.h │ ├── ExportKeywordViewController.m │ ├── ImportKeywordViewController.h │ └── ImportKeywordViewController.m ├── DMHY.entitlements ├── DMHY.xcdatamodeld │ ├── .xccurrentversion │ ├── DMHY v2.xcdatamodel │ │ └── contents │ ├── DMHY v3.xcdatamodel │ │ └── contents │ ├── DMHY v4.xcdatamodel │ │ └── contents │ └── DMHY.xcdatamodel │ │ └── contents ├── DMHYAPI.h ├── DMHYCoreDataStackManager.h ├── DMHYCoreDataStackManager.m ├── DMHYDownloader.h ├── DMHYDownloader.m ├── DMHYJSONDataManager.h ├── DMHYJSONDataManager.m ├── DMHYNotification.h ├── DMHYNotification.m ├── DMHYSiteChecker.h ├── DMHYSiteChecker.m ├── DMHYTool.h ├── DMHYTool.m ├── DMHYXMLDataManager.h ├── DMHYXMLDataManager.m ├── FileManagerViewController.h ├── FileManagerViewController.m ├── Info.plist ├── MASPreferencesViewController.h ├── MASPreferencesWindow.xib ├── MASPreferencesWindowController.h ├── MASPreferencesWindowController.m ├── MainSplitViewController.h ├── MainSplitViewController.m ├── Model │ ├── Bangumi.h │ ├── Bangumi.m │ ├── DMHYKeyword+CoreDataProperties.h │ ├── DMHYKeyword+CoreDataProperties.m │ ├── DMHYKeyword.h │ ├── DMHYKeyword.m │ ├── DMHYSite+CoreDataProperties.h │ ├── DMHYSite+CoreDataProperties.m │ ├── DMHYSite.h │ ├── DMHYSite.m │ ├── DMHYTorrent+CoreDataProperties.h │ ├── DMHYTorrent+CoreDataProperties.m │ ├── DMHYTorrent.h │ ├── DMHYTorrent.m │ ├── FileItem.h │ └── FileItem.m ├── NavigationView.h ├── NavigationView.m ├── Preference.xib ├── PreferenceController.h ├── PreferenceController.m ├── Preferences │ ├── SitePreferenceController.h │ ├── SitePreferenceController.m │ └── SitePreferenceController.xib ├── SavedDataViewController.h ├── SavedDataViewController.m ├── SideViewController.h ├── SideViewController.m ├── TitleTableCellView.h ├── TitleTableCellView.m ├── TorrentItem.h ├── TorrentItem.m ├── View │ ├── ButtonTableCellView.h │ ├── ButtonTableCellView.m │ ├── FileTableView.h │ ├── FileTableView.m │ ├── NSTableView+ContextMenu.h │ └── NSTableView+ContextMenu.m ├── ViewController.h ├── ViewController.m ├── ViewerPreferences.tiff ├── WindowController.h ├── WindowController.m ├── download.png ├── main.m ├── support-site.json └── zh-Hans.lproj │ └── Main.strings ├── LICENSE ├── Podfile ├── Podfile.lock └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | Pods/* 2 | -------------------------------------------------------------------------------- /DMHY.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /DMHY.xcodeproj/xcuserdata/yaqinking.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | DMHY.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 5 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /DMHY.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /DMHY.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /DMHY.xcworkspace/xcuserdata/yaqinking.xcuserdatad/UserInterfaceState.xcuserstate: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY.xcworkspace/xcuserdata/yaqinking.xcuserdatad/UserInterfaceState.xcuserstate -------------------------------------------------------------------------------- /DMHY/AppDelegate.h: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/9/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AppDelegate : NSObject 12 | 13 | @property (nonatomic) NSWindowController *preferencesWindowController; 14 | 15 | - (IBAction)togglePreviewPanel:(id)previewPanel; 16 | 17 | @end 18 | 19 | -------------------------------------------------------------------------------- /DMHY/AppDelegate.m: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/9/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "AppDelegate.h" 10 | #import "PreferenceController.h" 11 | #import "SitePreferenceController.h" 12 | #import "DMHYCoreDataStackManager.h" 13 | #import "DMHYKeyword+CoreDataProperties.h" 14 | #import "DMHYAPI.h" 15 | #import "DMHYTorrent.h" 16 | #import "MASPreferencesWindowController.h" 17 | 18 | @import Quartz; 19 | 20 | @interface AppDelegate () 21 | 22 | 23 | @property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext; 24 | 25 | @end 26 | 27 | @implementation AppDelegate 28 | 29 | @synthesize managedObjectContext = _context; 30 | 31 | - (void)applicationDidFinishLaunching:(NSNotification *)aNotification { 32 | 33 | } 34 | 35 | - (void)applicationWillTerminate:(NSNotification *)aNotification { 36 | // Insert code here to tear down your application 37 | } 38 | 39 | 40 | #pragma mark - MenuItem 41 | 42 | - (IBAction)showPreference:(id)sender { 43 | [self.preferencesWindowController showWindow:nil]; 44 | } 45 | 46 | - (IBAction)showDownloadPathInFinder:(id)sender { 47 | NSURL *savePath = [PreferenceController preferenceSavePath]; 48 | [[NSWorkspace sharedWorkspace] openURL:savePath]; 49 | } 50 | 51 | - (IBAction)deleteAllExistTorrents:(id)sender { 52 | NSFetchRequest *existTorrentRequest = [NSFetchRequest fetchRequestWithEntityName:@"Torrent"]; 53 | // existTorrentRequest.predicate = [NSPredicate predicateWithFormat:@"title LIKE %@",@"传颂"]; 54 | NSArray *torrents = [self.managedObjectContext executeFetchRequest:existTorrentRequest error:NULL]; 55 | for (DMHYTorrent *torrent in torrents) { 56 | [self.managedObjectContext deleteObject:torrent]; 57 | } 58 | [self.managedObjectContext save:NULL]; 59 | } 60 | 61 | - (IBAction)switchTheme:(id)sender { 62 | NSInteger themeCode = [PreferenceController preferenceTheme]; 63 | [PreferenceController setPreferenceTheme:(!themeCode)]; 64 | [DMHYNotification postNotificationName:DMHYThemeChangedNotification]; 65 | } 66 | 67 | - (IBAction)showHelp:(id)sender { 68 | [[NSWorkspace sharedWorkspace] openURL:[self helpURL]]; 69 | } 70 | 71 | - (IBAction)togglePreviewPanel:(id)previewPanel 72 | { 73 | if ([QLPreviewPanel sharedPreviewPanelExists] && [[QLPreviewPanel sharedPreviewPanel] isVisible]) 74 | { 75 | [[QLPreviewPanel sharedPreviewPanel] orderOut:nil]; 76 | } 77 | else 78 | { 79 | [[QLPreviewPanel sharedPreviewPanel] makeKeyAndOrderFront:nil]; 80 | } 81 | } 82 | 83 | - (NSURL *)helpURL { 84 | return [NSURL URLWithString:@"https://github.com/yaqinking/DMHY/wiki"]; 85 | } 86 | 87 | #pragma mark - Properties Initialization 88 | 89 | - (NSWindowController *)preferencesWindowController 90 | { 91 | if (_preferencesWindowController == nil) 92 | { 93 | NSViewController *generalViewController = [[PreferenceController alloc] init]; 94 | NSViewController *siteViewController = [[SitePreferenceController alloc] init]; 95 | 96 | NSArray *controllers = @[generalViewController, siteViewController]; 97 | 98 | // To add a flexible space between General and Advanced preference panes insert [NSNull null]: 99 | // NSArray *controllers = [[NSArray alloc] initWithObjects:generalViewController, [NSNull null], advancedViewController, nil]; 100 | 101 | NSString *title = @"设置"; 102 | _preferencesWindowController = [[MASPreferencesWindowController alloc] initWithViewControllers:controllers title:title]; 103 | 104 | } 105 | return _preferencesWindowController; 106 | } 107 | 108 | - (NSManagedObjectContext *)managedObjectContext { 109 | if (!_context) { 110 | _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 111 | _context.persistentStoreCoordinator = [[DMHYCoreDataStackManager sharedManager] persistentStoreCoordinator]; 112 | } 113 | return _context; 114 | } 115 | 116 | - (void)saveData { 117 | NSError *error = nil; 118 | if (![self.managedObjectContext save:&error]) { 119 | NSLog(@"Error %@",[error localizedDescription]); 120 | } 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "size" : "16x16", 5 | "idiom" : "mac", 6 | "filename" : "icon_16x16.png", 7 | "scale" : "1x" 8 | }, 9 | { 10 | "size" : "16x16", 11 | "idiom" : "mac", 12 | "filename" : "icon_16x16@2x.png", 13 | "scale" : "2x" 14 | }, 15 | { 16 | "size" : "32x32", 17 | "idiom" : "mac", 18 | "filename" : "icon_32x32.png", 19 | "scale" : "1x" 20 | }, 21 | { 22 | "size" : "32x32", 23 | "idiom" : "mac", 24 | "filename" : "icon_32x32@2x.png", 25 | "scale" : "2x" 26 | }, 27 | { 28 | "size" : "128x128", 29 | "idiom" : "mac", 30 | "filename" : "icon_128x128.png", 31 | "scale" : "1x" 32 | }, 33 | { 34 | "size" : "128x128", 35 | "idiom" : "mac", 36 | "filename" : "icon_128x128@2x.png", 37 | "scale" : "2x" 38 | }, 39 | { 40 | "size" : "256x256", 41 | "idiom" : "mac", 42 | "filename" : "icon_256x256.png", 43 | "scale" : "1x" 44 | }, 45 | { 46 | "size" : "256x256", 47 | "idiom" : "mac", 48 | "filename" : "icon_256x256@2x.png", 49 | "scale" : "2x" 50 | }, 51 | { 52 | "size" : "512x512", 53 | "idiom" : "mac", 54 | "filename" : "icon_512x512.png", 55 | "scale" : "1x" 56 | }, 57 | { 58 | "size" : "512x512", 59 | "idiom" : "mac", 60 | "filename" : "icon_512x512@2x.png", 61 | "scale" : "2x" 62 | } 63 | ], 64 | "info" : { 65 | "version" : 1, 66 | "author" : "xcode" 67 | } 68 | } -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/Assets.xcassets/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /DMHY/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /DMHY/Controller/AddKeywordViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // AddKeywordViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/17/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface AddKeywordViewController : NSViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/Controller/AddKeywordViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // AddKeywordViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/17/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "AddKeywordViewController.h" 10 | #import "DMHYCoreDataStackManager.h" 11 | 12 | @interface AddKeywordViewController () 13 | 14 | @property (weak) IBOutlet NSTextField *keywordTextField; 15 | @property (weak) IBOutlet NSPopUpButton *sitesPopUpButton; 16 | @property (weak) IBOutlet NSPopUpButton *weekdayPopUpButton; 17 | 18 | @end 19 | 20 | @implementation AddKeywordViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do view setup here. 25 | self.preferredContentSize = NSMakeSize(580, 140); 26 | [self setupData]; 27 | } 28 | 29 | - (void)setupData { 30 | NSMutableArray *siteNames = [[NSMutableArray alloc] init]; 31 | NSArray *sites = [[DMHYCoreDataStackManager sharedManager] allSites]; 32 | [sites enumerateObjectsUsingBlock:^(DMHYSite * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 33 | [siteNames addObject:obj.name]; 34 | }]; 35 | [self.sitesPopUpButton addItemsWithTitles:siteNames]; 36 | // A cheat at visual feeling 37 | NSArray *weekdays = @[@"周一", @"周二", @"周三", @"周四", @"周五", @"周六", @"周日", @"其他"]; 38 | [self.weekdayPopUpButton addItemsWithTitles:weekdays]; 39 | } 40 | 41 | - (IBAction)addKeyword:(id)sender { 42 | NSString *newKeyword = self.keywordTextField.stringValue; 43 | if ([newKeyword isEqualToString:@""]) { 44 | return; 45 | } 46 | NSMenuItem *siteItem = [self.sitesPopUpButton selectedItem]; 47 | NSInteger siteIndex = [self.sitesPopUpButton indexOfItem:siteItem]; 48 | DMHYSite *site = [[[DMHYCoreDataStackManager sharedManager] allSites] objectAtIndex:siteIndex]; 49 | 50 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"keyword == %@", self.weekdayPopUpButton.selectedItem.title]; 51 | DMHYKeyword *weekdayKeyword = [[[site.keywords allObjects] filteredArrayUsingPredicate:predicate] firstObject]; 52 | DMHYKeyword *keyword = [NSEntityDescription insertNewObjectForEntityForName:DMHYKeywordEntityKey inManagedObjectContext:[[DMHYCoreDataStackManager sharedManager] managedObjectContext]]; 53 | keyword.keyword = newKeyword; 54 | keyword.createDate = [NSDate new]; 55 | keyword.isSubKeyword = @YES; 56 | [weekdayKeyword addSubKeywordsObject:keyword]; 57 | [[DMHYCoreDataStackManager sharedManager] saveContext]; 58 | [DMHYNotification postNotificationName:DMHYKeywordAddedNotification object:site]; 59 | } 60 | 61 | @end 62 | -------------------------------------------------------------------------------- /DMHY/Controller/ExportKeywordViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ExportViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/18/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ExportKeywordViewController : NSViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/Controller/ExportKeywordViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ExportViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/18/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "ExportKeywordViewController.h" 10 | #import "DMHYCoreDataStackManager.h" 11 | 12 | @interface ExportKeywordViewController () 13 | @property (weak) IBOutlet NSStackView *stackView; 14 | @property (unsafe_unretained) IBOutlet NSTextView *textView; 15 | 16 | 17 | @property (nonatomic, strong) NSMutableArray *exportSiteNames; 18 | @end 19 | 20 | @implementation ExportKeywordViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | // Do view setup here. 25 | self.textView.minSize = NSMakeSize(200, 400); 26 | NSArray *sites = [[DMHYCoreDataStackManager sharedManager] allSites]; 27 | 28 | [sites enumerateObjectsUsingBlock:^(DMHYSite * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 29 | NSButton *button = [[NSButton alloc] initWithFrame:CGRectMake(0, 0, 150, 40)]; 30 | [button setButtonType:NSSwitchButton]; 31 | [button setBezelStyle:NSRoundedBezelStyle]; 32 | button.title = obj.name; 33 | button.target = self; 34 | button.action = @selector(checkButtonChanged:); 35 | [self.stackView insertArrangedSubview:button atIndex:idx]; 36 | }]; 37 | self.exportSiteNames = [NSMutableArray new]; 38 | } 39 | 40 | - (void)checkButtonChanged:(NSButton *)sender { 41 | if (sender.state == 1) { 42 | [self.exportSiteNames addObject:sender.title]; 43 | } else { 44 | [self.exportSiteNames removeObject:sender.title]; 45 | } 46 | } 47 | 48 | - (IBAction)export:(NSButton *)sender { 49 | [self.textView.textStorage.mutableString setString:@""]; 50 | [[DMHYCoreDataStackManager sharedManager] exportSites:self.exportSiteNames success:^(NSString *json){ 51 | [self.textView insertText:json]; 52 | } failure:^(NSError *error) { 53 | [NSApp presentError:error]; 54 | }]; 55 | } 56 | 57 | - (IBAction)copy:(id)sender { 58 | NSPasteboard *pasteboard = [NSPasteboard generalPasteboard]; 59 | [pasteboard clearContents]; 60 | [pasteboard writeObjects:@[self.textView.string]]; 61 | } 62 | 63 | @end 64 | -------------------------------------------------------------------------------- /DMHY/Controller/ImportKeywordViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ImportKeywordViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/18/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ImportKeywordViewController : NSViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/Controller/ImportKeywordViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ImportKeywordViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/18/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "ImportKeywordViewController.h" 10 | #import "DMHYCoreDataStackManager.h" 11 | 12 | @interface ImportKeywordViewController () 13 | 14 | @property (unsafe_unretained) IBOutlet NSTextView *jsonTextView; 15 | @property (weak) IBOutlet NSTextField *infoTextField; 16 | 17 | 18 | @end 19 | 20 | @implementation ImportKeywordViewController 21 | 22 | - (void)viewDidLoad { 23 | [super viewDidLoad]; 24 | self.preferredContentSize = NSMakeSize(617, 489); 25 | } 26 | 27 | - (IBAction)import:(id)sender { 28 | NSString *text = self.jsonTextView.textStorage.string; 29 | NSData *data = [text dataUsingEncoding:NSUTF8StringEncoding]; 30 | NSError *error = nil; 31 | NSDictionary *json = [NSJSONSerialization JSONObjectWithData:data options:NSJSONReadingAllowFragments error:&error]; 32 | if (error) { 33 | [NSApp presentError:error]; 34 | return; 35 | } 36 | NSArray *sites = json[@"sites"]; 37 | [[DMHYCoreDataStackManager sharedManager] importFromSites:sites success:^{ 38 | [[DMHYCoreDataStackManager sharedManager] saveContext]; 39 | self.infoTextField.stringValue = @"导入完毕"; 40 | } failure:^(NSError *error) { 41 | self.infoTextField.stringValue = @"导入失败"; 42 | }]; 43 | } 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /DMHY/DMHY.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.assets.movies.read-write 8 | 9 | com.apple.security.assets.music.read-write 10 | 11 | com.apple.security.assets.pictures.read-write 12 | 13 | com.apple.security.files.bookmarks.app-scope 14 | 15 | com.apple.security.files.downloads.read-write 16 | 17 | com.apple.security.files.user-selected.read-write 18 | 19 | com.apple.security.network.client 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DMHY/DMHY.xcdatamodeld/.xccurrentversion: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | _XCCurrentVersionName 6 | DMHY v4.xcdatamodel 7 | 8 | 9 | -------------------------------------------------------------------------------- /DMHY/DMHY.xcdatamodeld/DMHY v2.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DMHY/DMHY.xcdatamodeld/DMHY v3.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /DMHY/DMHY.xcdatamodeld/DMHY v4.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /DMHY/DMHY.xcdatamodeld/DMHY.xcdatamodel/contents: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /DMHY/DMHYAPI.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYAPI.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 15/8/31. 6 | // Copyright © 2015年 yaqinking. All rights reserved. 7 | // 8 | 9 | #ifndef DMHYAPI_h 10 | #define DMHYAPI_h 11 | 12 | #define kXpathTorrentDownloadShortURL @"//div[@class='dis ui-tabs-panel ui-widget-content ui-corner-bottom']/a/@href" 13 | #define kXpathTorrentDirectDownloadLink @"//div[@class='dis']//p//a//@href" 14 | 15 | #define kXPathTorrentItem @"//item" 16 | #define kDownloadLinkType @"DownloadLinkType" 17 | #define kDownloadSite @"DownloadSite" 18 | #define kSavePath @"SavePath" 19 | #define kFileWatchPath @"FileWatchPath" 20 | #define kSelectKeyword @"SelectKeyword" 21 | #define kSelectKeywordIsSubKeyword @"SelectKeywordIsSubKeyword" 22 | #define kWeekdayOther @"其他" 23 | #define kMainViewRowStyle @"MainViewRowStyle" 24 | #define kMainTableViewRowStyle @"MainTableViewRowStyle" 25 | #define kDoubleAction @"DoubleAction" 26 | 27 | #define DMHYMainTableViewRowStyleChangedNotification @"DMHYMainTableViewRowStyleChangedNotification" 28 | #define DMHYDoubleActionChangedNotification @"DMHYDoubleActionChangedNotification" 29 | #define kFetchInterval @"FetchInterval" 30 | #define kFetchIntervalMinimum 300 //5 minutes 31 | #define kFetchIntervalMaximun 43200 //12 hours 32 | 33 | #define kFileWatchInterval @"FileWatchInterval" 34 | #define kFileWatchIntervalMinimum 60 //1 minutes 35 | #define kFileWatchIntervalMaximum 43200 36 | 37 | #define DMHYThemeChangedNotification @"DMHYThemeChangedNotification" 38 | #define DMHYSavePathChangedNotification @"DMHYSavaPathChangedNotification" 39 | #define DMHYFileWatchPathChangedNotification @"DMHYFileWatchPathChangedNotification" 40 | #define DMHYDatabaseChangedNotification @"DMHYDatabaseChangedNotification" 41 | #define DMHYDownloadLinkTypeNotification @"DMHYDownloadLinkTypeNotification" 42 | #define DMHYAutoDownloadSiteChangedNotification @"DMHYAutoDownloadSiteChangedNotification" 43 | #define DMHYDoubleActionChangedNotification @"DMHYDoubleActionChangedNotification" 44 | #define DMHYSelectKeywordChangedNotification @"DMHYSelectKeywordChangedNotification" 45 | #define DMHYFetchIntervalChangedNotification @"DMHYFetchIntervalChangedNotification" 46 | 47 | #define DMHYMainTableViewRowStyleChangedNotification @"DMHYMainTableViewRowStyleChangedNotification" 48 | #define DMHYFileWatchIntervalChangedNotification @"DMHYFileWatchIntervalChangedNotification" 49 | 50 | #define DMHYThemeKey @"ThemeType" 51 | 52 | typedef NS_ENUM(NSInteger, DMHYThemeType) { 53 | DMHYThemeLight, 54 | DMHYThemeDark 55 | }; 56 | 57 | typedef NS_ENUM(NSInteger, DMHYSiteType) { 58 | DMHYSiteDefault, 59 | DMHYSiteDandanplay, 60 | DMHYSiteACGGG, 61 | DMHYSiteBangumiMoe 62 | }; 63 | 64 | static NSString * const AppDomain = @"com.yaqinking.DMHY"; 65 | 66 | static NSString * const DMHYSiteEntityKey = @"Site"; 67 | static NSString * const DMHYKeywordEntityKey = @"Keyword"; 68 | static NSString * const DMHYTorrentEntityKey = @"Torrent"; 69 | 70 | static NSString * const DMHYSiteNameKey = @"site_name"; 71 | static NSString * const DMHYSiteMainURLKey = @"main_url"; 72 | static NSString * const DMHYSiteSearchURLKey = @"search_url"; 73 | static NSString * const DMHYSiteFliterKey = @"fliter_site"; 74 | static NSString * const DMHYSiteAutoDLKey = @"auto_download"; 75 | static NSString * const DMHYSiteDLTypeKey = @"download_type"; 76 | static NSString * const DMHYSiteDLFinKey = @"download_fin"; 77 | static NSString * const DMHYSiteResponseTypeKey = @"response_type"; 78 | static NSString * const DMHYSiteCurrentUseKey = @"current_use"; 79 | static NSString * const DMHYSiteKeywordsKey = @"keywords"; 80 | 81 | static NSString * const DMHYKeywordAddedNotification = @"moe.yaqinking.dmhy.added.keyword.notification"; 82 | static NSString * const DMHYSearchSiteChangedNotification = @"moe.yaqinking.dmhy.search.site.changed.notification"; 83 | static NSString * const DMHYSiteAddedNotification = @"moe.yaqinking.dmhy.site.added.notification"; 84 | static NSString * const DMHYKeywordCheckedNotification = @"moe.yaqinking.dmhy.keyword.checked.notification"; 85 | 86 | #import "DMHYTool.h" 87 | #import "DMHYNotification.h" 88 | #import "DMHYDownloader.h" 89 | 90 | #endif /* DMHYAPI_h */ 91 | -------------------------------------------------------------------------------- /DMHY/DMHYCoreDataStackManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYCoreDataStackManager.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/22/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DMHYAPI.h" 11 | #import "DMHYSite.h" 12 | #import "DMHYKeyword.h" 13 | #import "DMHYTorrent.h" 14 | 15 | extern NSString * const DMHYSeedDataCompletedNotification; 16 | 17 | @import Cocoa; 18 | 19 | @interface DMHYCoreDataStackManager : NSObject 20 | 21 | @property (nonatomic, readonly) NSManagedObjectModel *managedObjectModel; 22 | @property (nonatomic, readonly) NSPersistentStoreCoordinator *persistentStoreCoordinator; 23 | @property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext; 24 | 25 | @property (nonatomic, readonly) NSURL *storeURL; 26 | @property (nonatomic, readonly) NSURL *applicationDocumentsDirectory; 27 | 28 | + (instancetype)sharedManager; 29 | - (BOOL)resetDatabase; 30 | 31 | - (void)seedDataIfNeed; 32 | - (void)saveContext; 33 | 34 | 35 | 36 | - (DMHYSite *)querySiteWithSiteName:(NSString *)siteName; 37 | - (void)addDefaultWeekdaysToSite:(DMHYSite *)site; 38 | 39 | /** 40 | * Insert multi keywords 41 | * (site->weeday->keywords) 42 | * 43 | * @param keywords Keywords array to insert 44 | * @param weekday The keywords is belong to this weekday 45 | * @param site The weekday belong to this site 46 | */ 47 | - (void)insertKeywords:(NSArray *)keywords weekday:(NSString *)weekday site:(DMHYSite *) site; 48 | 49 | - (DMHYSite *)currentUseSite; 50 | - (NSArray *)autoDownloadSites; 51 | - (NSArray *)allSites; 52 | 53 | - (BOOL)existTorrentWithTitle:(NSString *)title; 54 | 55 | - (void)importFromSites:(NSArray *)sites success:(void(^)()) successHandler failure:(void(^)(NSError *error)) failureHandler; 56 | - (void)exportSites:(NSArray *)siteNames success:(void(^)(NSString *json)) successHandler failure:(void(^)(NSError *error)) failureHandler; 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /DMHY/DMHYDownloader.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYDownloader.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/24/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DMHYAPI.h" 11 | #import "DMHYNotification.h" 12 | #import "AFNetworking.h" 13 | #import "PreferenceController.h" 14 | #import "Ono.h" 15 | 16 | /** 17 | * 下载给定链接的种子 18 | */ 19 | @interface DMHYDownloader : NSObject 20 | 21 | + (DMHYDownloader *)downloader; 22 | 23 | /** 24 | * Download torrent with given url and push a local notification to notify use downloaded 25 | * 26 | * @param url Download file url 27 | */ 28 | - (void)downloadTorrentWithURL:(NSURL *)url; 29 | /** 30 | * Download torrent from share.dmhy.org torrent description page 31 | * 1. Exract torrent download url 32 | * @param url dmhy bangumi description page 33 | */ 34 | - (void)downloadTorrentFromPageURLString:(NSString *)urlString; 35 | 36 | - (void)downloadTorrentFromPageURLString:(NSString *)urlString willStartBlock:(void(^)()) startBlock success:(void(^)()) successHandler failure:(void(^)(NSError *error)) failureHandler; 37 | 38 | @end 39 | -------------------------------------------------------------------------------- /DMHY/DMHYDownloader.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYDownloader.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/24/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYDownloader.h" 10 | 11 | @interface DMHYDownloader() 12 | 13 | @property (nonatomic, strong) AFURLSessionManager *manager; 14 | 15 | @end 16 | 17 | @implementation DMHYDownloader 18 | 19 | + (DMHYDownloader *)downloader { 20 | static DMHYDownloader *downloader = nil; 21 | static dispatch_once_t onceToken; 22 | dispatch_once(&onceToken, ^{ 23 | downloader = [[DMHYDownloader alloc] init]; 24 | }); 25 | return downloader; 26 | } 27 | 28 | - (void)downloadTorrentWithURL:(NSURL *)url { 29 | NSURLRequest *request = [NSURLRequest requestWithURL:url]; 30 | NSURLSessionDownloadTask *downloadTask = [self.manager downloadTaskWithRequest:request 31 | progress:nil 32 | destination:^NSURL * _Nonnull(NSURL * _Nonnull targetPath, NSURLResponse * _Nonnull response) { 33 | NSURL *savePath = [PreferenceController preferenceSavePath]; 34 | if (!savePath) { 35 | savePath = [PreferenceController userDownloadPath]; 36 | } 37 | return [savePath URLByAppendingPathComponent:[response suggestedFilename]]; 38 | 39 | } completionHandler:^(NSURLResponse * _Nonnull response, NSURL * _Nonnull filePath, NSError * _Nonnull error) { 40 | NSString *fileName = [response suggestedFilename]; 41 | [self postUserNotificationWithFileName:fileName]; 42 | 43 | }]; 44 | [downloadTask resume]; 45 | } 46 | 47 | - (void)downloadTorrentFromPageURLString:(NSString *)urlString { 48 | [self downloadTorrentFromPageURLString:urlString willStartBlock:nil success:nil failure:nil]; 49 | } 50 | 51 | - (void)downloadTorrentFromPageURLString:(NSString *)urlString willStartBlock:(void (^)())startBlock success:(void (^)())successHandler failure:(void (^)(NSError *))failureHandler { 52 | if (startBlock) { 53 | startBlock(); 54 | } 55 | AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 56 | manager.responseSerializer = [AFHTTPResponseSerializer serializer]; 57 | [manager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 58 | dispatch_async(dispatch_queue_create("download queue", nil), ^{ 59 | ONOXMLDocument *doc = [ONOXMLDocument HTMLDocumentWithData:responseObject error:nil]; 60 | __block NSMutableString *downloadString = [NSMutableString new]; 61 | [doc enumerateElementsWithXPath:kXpathTorrentDirectDownloadLink usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) { 62 | downloadString = [[element stringValue] mutableCopy]; 63 | *stop = YES; 64 | }]; 65 | 66 | NSURL *dlURL = [NSURL URLWithString:[NSString stringWithFormat:@"https:%@",downloadString]]; 67 | [self downloadTorrentWithURL:dlURL]; 68 | if (successHandler) { 69 | successHandler(); 70 | } 71 | }); 72 | } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 73 | if (failureHandler) { 74 | failureHandler(error); 75 | } 76 | }]; 77 | } 78 | 79 | #pragma mark - LocalNotification 80 | 81 | - (void)postUserNotificationWithFileName:(NSString *)fileName { 82 | NSUserNotification *notification = [[NSUserNotification alloc] init]; 83 | notification.title = @"下载完成"; 84 | notification.informativeText = fileName; 85 | notification.soundName = NSUserNotificationDefaultSoundName; 86 | [[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:notification]; 87 | } 88 | 89 | - (AFURLSessionManager *)manager { 90 | if (!_manager) { 91 | NSURLSessionConfiguration *conf = [NSURLSessionConfiguration defaultSessionConfiguration]; 92 | _manager = [[AFURLSessionManager alloc] initWithSessionConfiguration:conf]; 93 | } 94 | return _manager; 95 | } 96 | 97 | @end 98 | -------------------------------------------------------------------------------- /DMHY/DMHYJSONDataManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYJSONManager.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/31/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @class TorrentItem; 12 | 13 | extern NSString * const DMHYBangumiMoeOpenTorrentPagePrefixFormat; 14 | extern NSString * const publishTimeKey; 15 | extern NSString * const idKey; 16 | extern NSString * const teamNameKeyPath; 17 | extern NSString * const uploaderUserNameKeyPath; 18 | extern NSString * const magnetKey; 19 | 20 | typedef void (^DMHYJSONDataFetchSuccessBlock)(NSArray *objects); 21 | typedef void (^DMHYJSONDataFetchFailureBlock)(NSError *error); 22 | 23 | @interface DMHYJSONDataManager : NSObject 24 | 25 | + (DMHYJSONDataManager *)manager; 26 | 27 | - (void)GET:(NSString *)urlString success:(DMHYJSONDataFetchSuccessBlock) successBlock failure:(DMHYJSONDataFetchFailureBlock) failureBlock; 28 | 29 | @end 30 | -------------------------------------------------------------------------------- /DMHY/DMHYJSONDataManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYJSONManager.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/31/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYJSONDataManager.h" 10 | #import "AFNetworking.h" 11 | #import "TorrentItem.h" 12 | 13 | NSString * const DMHYBangumiMoeOpenTorrentPagePrefixFormat = @"https://bangumi.moe/torrent/%@"; 14 | NSString * const publishTimeKey = @"publish_time"; 15 | NSString * const idKey = @"_id"; 16 | NSString * const teamNameKeyPath = @"team.name"; 17 | NSString * const uploaderUserNameKeyPath = @"uploader.username"; 18 | NSString * const magnetKey = @"magnet"; 19 | 20 | @interface DMHYJSONDataManager() 21 | 22 | @property (nonatomic, strong) AFHTTPSessionManager *httpManager; 23 | 24 | @end 25 | 26 | @implementation DMHYJSONDataManager 27 | 28 | + (DMHYJSONDataManager *)manager { 29 | static DMHYJSONDataManager *sharedManager = nil; 30 | static dispatch_once_t onceToken; 31 | dispatch_once(&onceToken, ^{ 32 | sharedManager = [[DMHYJSONDataManager alloc] init]; 33 | }); 34 | return sharedManager; 35 | 36 | } 37 | 38 | - (void)GET:(NSString *)urlString success:(DMHYJSONDataFetchSuccessBlock)successBlock failure:(DMHYJSONDataFetchFailureBlock)failureBlock { 39 | [self.httpManager GET:urlString 40 | parameters:nil progress:nil 41 | success:^(NSURLSessionDataTask * _Nonnull task, id _Nonnull responseObject) { 42 | NSArray *torrentsArray = [responseObject valueForKey:@"torrents"]; 43 | NSMutableArray *data = [NSMutableArray new]; 44 | for (NSDictionary *element in torrentsArray) { 45 | TorrentItem *item = [[TorrentItem alloc] init]; 46 | NSString *pubDate = element[publishTimeKey]; 47 | NSString *link = element[idKey]; 48 | NSString *team_name = [element valueForKeyPath:teamNameKeyPath]; 49 | if (!team_name) { 50 | team_name = [element valueForKeyPath:uploaderUserNameKeyPath]; 51 | } 52 | item.title = element[@"title"]; 53 | item.magnet = [NSURL URLWithString:element[magnetKey]]; 54 | item.link = [NSURL URLWithString:[NSString stringWithFormat:DMHYBangumiMoeOpenTorrentPagePrefixFormat,link]]; //如果返回的 URL 里没有前缀的话,在这里也要处理 55 | item.pubDate = pubDate; 56 | item.author = team_name; 57 | [data addObject:item]; 58 | } 59 | successBlock(data); 60 | } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 61 | // NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *)task.response; 62 | NSLog(@"XMLDataManager Request Error %@",[error localizedDescription]); 63 | // NSNumber *statusCode = [NSNumber numberWithInteger:httpResponse.statusCode]; 64 | failureBlock(error); 65 | }]; 66 | } 67 | 68 | - (AFHTTPSessionManager *)httpManager { 69 | if (!_httpManager) { 70 | _httpManager = [AFHTTPSessionManager manager]; 71 | _httpManager.responseSerializer = [AFJSONResponseSerializer serializer]; 72 | } 73 | return _httpManager; 74 | } 75 | 76 | @end 77 | -------------------------------------------------------------------------------- /DMHY/DMHYNotification.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYNotification.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/23/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DMHYNotification : NSObject 12 | 13 | + (void)postNotificationName:(NSString *) name; 14 | + (void)postNotificationName:(NSString *)name userInfo:(id)info; 15 | + (void)postNotificationName:(NSString *)name object:(id)object; 16 | + (void)postNotificationName:(NSString *)name object:(id)object userInfo:(id)info; 17 | + (void)addObserver:(id)object selector:(SEL) selector name:(NSString *)name; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DMHY/DMHYNotification.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYNotification.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/23/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYNotification.h" 10 | 11 | @implementation DMHYNotification 12 | 13 | + (void)postNotificationName:(NSString *)name { 14 | [[NSNotificationCenter defaultCenter] postNotificationName:name object:nil]; 15 | } 16 | 17 | + (void)postNotificationName:(NSString *)name userInfo:(id)info { 18 | [[NSNotificationCenter defaultCenter] postNotificationName:name object:nil userInfo:info]; 19 | } 20 | 21 | + (void)postNotificationName:(NSString *)name object:(id)object { 22 | [[NSNotificationCenter defaultCenter] postNotificationName:name object:object]; 23 | } 24 | 25 | + (void)postNotificationName:(NSString *)name object:(id)object userInfo:(id)info { 26 | [[NSNotificationCenter defaultCenter] postNotificationName:name object:object userInfo:info]; 27 | } 28 | 29 | + (void)addObserver:(id)object selector:(SEL)selector name:(NSString *)name { 30 | [[NSNotificationCenter defaultCenter] addObserver:object 31 | selector:selector 32 | name:name 33 | object:nil]; 34 | } 35 | 36 | @end 37 | -------------------------------------------------------------------------------- /DMHY/DMHYSiteChecker.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYSiteChecker.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * 检查站点数据是否有更新 13 | */ 14 | @interface DMHYSiteChecker : NSObject 15 | 16 | - (void)invalidateTimer; 17 | - (void)startWitchCheckInterval:(NSTimeInterval )checkInterval; 18 | - (void)automaticDownloadTorrent; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /DMHY/DMHYSiteChecker.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYSiteChecker.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYSiteChecker.h" 10 | #import "DMHYTool.h" 11 | #import "DMHYAPI.h" 12 | #import "DMHYCoreDataStackManager.h" 13 | #import "AFNetworking.h" 14 | #import "Ono.h" 15 | #import "DMHYXMLDataManager.h" 16 | #import "DMHYJSONDataManager.h" 17 | 18 | @interface DMHYSiteChecker() 19 | 20 | @property (nonatomic, strong) NSTimer *timer; 21 | @property (nonatomic, strong) NSString *today; 22 | @property (nonatomic, strong) NSDateFormatter *dateFormater; 23 | 24 | @property (nonatomic) BOOL isMagnetLink; 25 | @property (nonatomic) BOOL dontDownloadCollection; 26 | 27 | @end 28 | 29 | @implementation DMHYSiteChecker 30 | 31 | - (void)invalidateTimer { 32 | [self.timer invalidate]; 33 | self.timer = nil; 34 | } 35 | 36 | 37 | - (void)startWitchCheckInterval:(NSTimeInterval)checkInterval { 38 | if (checkInterval >= 300) { 39 | self.timer = [NSTimer scheduledTimerWithTimeInterval:checkInterval 40 | target:self 41 | selector:@selector(automaticDownloadTorrent) 42 | userInfo:nil 43 | repeats:YES]; 44 | } else { 45 | NSString *reason = [NSString stringWithFormat:@"\nFetch interval value is invalid. Current %li", (long)checkInterval]; 46 | NSString *suggestion = [NSString stringWithFormat:@"You can open Terminal.app type\n\ndefaults write %@ FetchInterval 300\n\nThen press Enter key.\nRestart app to fix it.", AppDomain]; 47 | NSDictionary *userInfo = @{ NSLocalizedFailureReasonErrorKey : reason, 48 | NSLocalizedRecoverySuggestionErrorKey : suggestion}; 49 | NSError *error = [NSError errorWithDomain:AppDomain code:4444 userInfo:userInfo]; 50 | [NSApp presentError:error]; 51 | [NSApp terminate:self]; 52 | } 53 | } 54 | 55 | /** 56 | * Check wheather today has new torrent. 其它分类下的每次都 fetch。 57 | * Then invoke downloadNewTorrents: 58 | * @param weekday today string 59 | */ 60 | - (void)automaticDownloadTorrent { 61 | NSDate *now = [NSDate new]; 62 | NSCalendar* cal = [NSCalendar currentCalendar]; 63 | NSDateComponents *com = [cal components:NSCalendarUnitWeekday fromDate:now]; 64 | NSInteger weekdayToday = [com weekday];// 1 = Sunday, 2 = Monday, etc. 65 | self.today = [DMHYTool cn_weekdayFromWeekdayCode:weekdayToday]; 66 | 67 | NSArray *sites = [[DMHYCoreDataStackManager sharedManager] autoDownloadSites]; 68 | [sites enumerateObjectsUsingBlock:^(DMHYSite * _Nonnull site, NSUInteger idx, BOOL * _Nonnull stop) { 69 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"keyword == %@ OR keyword == %@", self.today, kWeekdayOther]; 70 | NSArray *flitedKeywords = [[site.keywords allObjects] filteredArrayUsingPredicate:predicate]; 71 | [flitedKeywords enumerateObjectsUsingBlock:^(DMHYKeyword * _Nonnull parentKeyword, NSUInteger idx, BOOL * _Nonnull stop) { 72 | [parentKeyword.subKeywords enumerateObjectsUsingBlock:^(DMHYKeyword * _Nonnull keyword, BOOL * _Nonnull stop) { 73 | if ([site.responseType isEqualToString:@"xml"]) { 74 | [[DMHYXMLDataManager manager] fetchFromSite:site queryKeyword:keyword fetchedNew:^(DMHYTorrent *torrent) { 75 | if ([site.name isEqualToString:@"dmhy"]) { 76 | [[DMHYDownloader downloader] downloadTorrentFromPageURLString:torrent.link]; 77 | return; 78 | } 79 | if ([site.name containsString:@"nyaa.se"]) { 80 | [[DMHYDownloader downloader] downloadTorrentWithURL:[NSURL URLWithString:torrent.link]]; 81 | return; 82 | } else { 83 | NSURL *url = [NSURL URLWithString:torrent.magnet]; 84 | // acg.rip contains .torrent bt.acg.gg contains down.php 85 | if ([torrent.magnet containsString:@".torrent"] || 86 | [torrent.magnet containsString:@"down.php"]) { 87 | [[DMHYDownloader downloader] downloadTorrentWithURL:url]; 88 | // Think this wil place to downloader completion handler? 89 | torrent.isDownloaded = @YES; 90 | torrent.isNewTorrent = @NO; 91 | } 92 | if ([torrent.magnet containsString:@"magnet:?xt=urn:btih:"]) { 93 | [[NSWorkspace sharedWorkspace] openURL:url]; 94 | torrent.isDownloaded = @YES; 95 | torrent.isNewTorrent = @NO; 96 | } 97 | } 98 | } completion:^{ 99 | NSLog(@"Site %@ Keyword %@ Check completed.", site.name, keyword.keyword); 100 | // When all task complated save changes. 101 | [[DMHYCoreDataStackManager sharedManager] saveContext]; 102 | [DMHYNotification postNotificationName:DMHYKeywordCheckedNotification userInfo:@{@"site": site.name, 103 | @"keyword": keyword.keyword}]; 104 | } failure:^(NSError *error) { 105 | NSLog(@"Site %@ Keyword %@ Check error %@", site.name, keyword.keyword, [error localizedDescription]); 106 | }]; 107 | } 108 | if ([site.responseType isEqualToString:@"json"]) { 109 | 110 | } 111 | }]; 112 | }]; 113 | }]; 114 | } 115 | 116 | 117 | #pragma mark - Download 118 | 119 | - (void)openMagnetWith:(NSURL *)magnet { 120 | [[NSWorkspace sharedWorkspace] openURL:magnet]; 121 | } 122 | 123 | @end 124 | -------------------------------------------------------------------------------- /DMHY/DMHYTool.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYTool.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 16/1/21. 6 | // Copyright © 2016年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface DMHYTool : NSObject 12 | 13 | /** 14 | * Some useful tool 15 | * 16 | * @return DMHYToll shared instance 17 | */ 18 | + (DMHYTool *)tool; 19 | 20 | /** 21 | * Get chinese weekday. 22 | * 1 周日 2 周一 3 周二 4 周三 5 周四 6 周五 7 周日 0 周六 -1 周五 23 | * @param weekday the weekday code. -1 to 7 24 | * 25 | * @return Chinese weekday string. 26 | */ 27 | + (NSString *) cn_weekdayFromWeekdayCode:(NSInteger )weekday; 28 | 29 | /** 30 | * Convert a url string to valided URL. 31 | * 32 | * @param urlString The url string maybe contain chinese. 33 | * 34 | * @return The valided URL. 35 | */ 36 | + (NSURL *)convertToURLWithURLString:(NSString *)urlString; 37 | 38 | /** 39 | * Get bangumi season by month 40 | * 41 | * @param month The current month code 42 | * 43 | * @return belong to season 44 | */ 45 | + (NSString *) bangumiSeasonOfMonth:(NSInteger )month; 46 | 47 | /** 48 | * Get nice looked date string from dmhy original date string 49 | * 50 | * @param dateString 51 | * 52 | * @return Nice looked date string 53 | */ 54 | - (NSString *)formatedDateStringFromDMHYDateString:(NSString *)dateString; 55 | 56 | /** 57 | * Get date from dateString 58 | * 59 | * @param dateString 60 | * 61 | * @return A formated date 62 | */ 63 | - (NSDate *)formatedDateFromDMHYDateString:(NSString *)dateString; 64 | 65 | 66 | - (NSString *)stringFromSavedDate:(NSDate *)date; 67 | - (NSString *)infoDateStringFromDate:(NSDate *)date; 68 | 69 | @end 70 | -------------------------------------------------------------------------------- /DMHY/DMHYTool.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYTool.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 16/1/21. 6 | // Copyright © 2016年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYTool.h" 10 | #import "NSDate+DateTools.h" 11 | 12 | NSInteger const kWeekTimeInterval = -7*24*60*60; 13 | 14 | @interface DMHYTool () 15 | 16 | @property (nonatomic, strong) NSDateFormatter *dateFormater; 17 | 18 | @end 19 | 20 | @implementation DMHYTool 21 | 22 | + (DMHYTool *)tool { 23 | static DMHYTool *tool = nil; 24 | static dispatch_once_t onceToken; 25 | dispatch_once(&onceToken, ^{ 26 | tool = [[DMHYTool alloc] init]; 27 | }); 28 | return tool; 29 | } 30 | 31 | + (NSString *) cn_weekdayFromWeekdayCode:(NSInteger )weekday { 32 | switch (weekday) { 33 | case 1: 34 | return @"周日"; 35 | case 2: 36 | return @"周一"; 37 | case 3: 38 | return @"周二"; 39 | case 4: 40 | return @"周三"; 41 | case 5: 42 | return @"周四"; 43 | case 6: 44 | return @"周五"; 45 | case 7: 46 | return @"周六"; 47 | // for bgmlist 6 代表周六 但 JP 上映时间是 晚上 0000 之前的话,CN 则是 周日,然后取出值之后 +2 则是通用从 bgmlist 周几转到 dmhy 周几的 code。所以周六 bgmlist code 6 而 +2 之后是 8 但是是周日中国上映,8 在 KeywordViewController 里的bgmlist weekdaycode 中代表是 周日。 48 | case 8: 49 | return @"周日"; 50 | // for yesterday and day before yesterday 1-1=0 1-2=-1 2-2=0; 51 | case 0: 52 | return @"周六"; 53 | case -1: 54 | return @"周五"; 55 | default: 56 | break; 57 | } 58 | return @""; 59 | } 60 | 61 | + (NSURL *)convertToURLWithURLString:(NSString *)urlString { 62 | NSCharacterSet *set = [NSCharacterSet URLQueryAllowedCharacterSet]; 63 | NSString *escapedString = [urlString stringByAddingPercentEncodingWithAllowedCharacters:set]; 64 | return [NSURL URLWithString:escapedString]; 65 | } 66 | 67 | + (NSString *)bangumiSeasonOfMonth:(NSInteger )month { 68 | switch (month) { 69 | case 1: 70 | case 2: 71 | case 3: 72 | return @"01"; 73 | case 4: 74 | case 5: 75 | case 6: 76 | return @"04"; 77 | case 7: 78 | case 8: 79 | case 9: 80 | return @"07"; 81 | case 10: 82 | case 11: 83 | case 12: 84 | return @"10"; 85 | default: 86 | break; 87 | } 88 | return @""; 89 | } 90 | 91 | - (NSString *)formatedDateStringFromDMHYDateString:(NSString *)dateString { 92 | // NSLog(@"dateFormater %@",self.dateFormater); 93 | self.dateFormater.dateFormat = @"EEE, dd MM yyyy HH:mm:ss Z"; 94 | self.dateFormater.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 95 | NSDate *longDate = [self.dateFormater dateFromString:dateString]; 96 | NSDate *oneWeekAgo = [NSDate dateWithTimeIntervalSinceNow:kWeekTimeInterval]; 97 | if ( [longDate isLaterThanOrEqualTo:oneWeekAgo]) { 98 | return longDate.timeAgoSinceNow; 99 | } else { 100 | self.dateFormater.dateFormat = @"EEE HH:mm:ss yy-MM-dd"; 101 | self.dateFormater.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]; 102 | return [self.dateFormater stringFromDate:longDate]; 103 | } 104 | } 105 | 106 | - (NSDate *)formatedDateFromDMHYDateString:(NSString *)dateString { 107 | self.dateFormater.dateFormat = @"EEE, dd MM yyyy HH:mm:ss Z"; 108 | self.dateFormater.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"en_US"]; 109 | NSDate *longDate = [self.dateFormater dateFromString:dateString]; 110 | return longDate; 111 | } 112 | 113 | - (NSString *)stringFromSavedDate:(NSDate *)date { 114 | self.dateFormater.dateFormat = @"EEE HH:mm:ss yy-MM-dd"; 115 | self.dateFormater.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]; 116 | return [self.dateFormater stringFromDate:date]; 117 | } 118 | 119 | - (NSString *)infoDateStringFromDate:(NSDate *)date { 120 | self.dateFormater.dateFormat = @"MM月dd日 HH:mm:ss"; 121 | self.dateFormater.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]; 122 | return [self.dateFormater stringFromDate:date]; 123 | } 124 | 125 | - (NSDateFormatter *)dateFormater { 126 | if (!_dateFormater) { 127 | _dateFormater = [[NSDateFormatter alloc] init]; 128 | } 129 | return _dateFormater; 130 | } 131 | 132 | @end 133 | -------------------------------------------------------------------------------- /DMHY/DMHYXMLDataManager.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYXMLDataManager.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/31/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "DMHYCoreDataStackManager.h" 11 | 12 | @class TorrentItem; 13 | 14 | extern NSString * const pubDateKey; 15 | extern NSString * const titleKey; 16 | extern NSString * const linkKey; 17 | extern NSString * const authorKey; 18 | extern NSString * const kXPathItem; 19 | 20 | typedef void (^DMHYXMLDataFetchSuccessBlock)(NSArray *objects); 21 | typedef void (^DMHYXMLDataFetchFailureBlock)(NSError *error); 22 | typedef void (^DMHYXMLDataFetchedNewTorrentBlock)(DMHYTorrent *torrent); 23 | typedef void (^DMHYXMLDataFetchCompletionBlock)(); 24 | 25 | @interface DMHYXMLDataManager : NSObject 26 | 27 | @property (nonatomic, readonly) DMHYXMLDataFetchSuccessBlock successBlock; 28 | @property (nonatomic, readonly) DMHYXMLDataFetchFailureBlock failureBlock; 29 | 30 | + (DMHYXMLDataManager *)manager; 31 | 32 | - (void)GET:(NSString *)urlString success:(DMHYXMLDataFetchSuccessBlock) successBlock failure:(DMHYXMLDataFetchFailureBlock) failureBlock; 33 | - (void)fetchFromSite:(DMHYSite *) site queryKeyword:(DMHYKeyword *)keyword fetchedNew:(DMHYXMLDataFetchedNewTorrentBlock) block completion:(DMHYXMLDataFetchCompletionBlock) completionHandler failure:(DMHYXMLDataFetchFailureBlock) failureHandler; 34 | 35 | @end 36 | -------------------------------------------------------------------------------- /DMHY/DMHYXMLDataManager.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYXMLDataManager.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/31/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYXMLDataManager.h" 10 | #import "AFNetworking.h" 11 | #import "Ono.h" 12 | #import "TorrentItem.h" 13 | #import "DMHYTool.h" 14 | 15 | NSString * const pubDateKey = @"pubDate"; 16 | NSString * const titleKey = @"title"; 17 | NSString * const linkKey = @"link"; 18 | NSString * const authorKey = @"author"; 19 | 20 | NSString * const kXPathItem = @"//item"; 21 | 22 | @interface DMHYXMLDataManager() 23 | 24 | @property (nonatomic, strong) NSSet *accepableContentTypes; 25 | @property (nonatomic, strong) NSManagedObjectContext *managedObjectContext; 26 | 27 | @end 28 | 29 | @implementation DMHYXMLDataManager 30 | 31 | + (DMHYXMLDataManager *)manager { 32 | static DMHYXMLDataManager *sharedManager = nil; 33 | static dispatch_once_t onceToken; 34 | dispatch_once(&onceToken, ^{ 35 | sharedManager = [[DMHYXMLDataManager alloc] init]; 36 | }); 37 | return sharedManager; 38 | } 39 | - (void)GET:(NSString *)urlString success:(DMHYXMLDataFetchSuccessBlock)successBlock failure:(DMHYXMLDataFetchFailureBlock)failureBlock { 40 | _successBlock = successBlock; 41 | _failureBlock = failureBlock; 42 | AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 43 | manager.responseSerializer = [AFXMLDocumentResponseSerializer serializer]; 44 | manager.responseSerializer.acceptableContentTypes = self.accepableContentTypes; 45 | __block NSMutableArray *torrents = [NSMutableArray new]; 46 | 47 | [manager GET:urlString parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 48 | ONOXMLDocument *xmlDoc = [ONOXMLDocument XMLDocumentWithString:[responseObject description] encoding:NSUTF8StringEncoding error:nil]; 49 | [xmlDoc enumerateElementsWithXPath:kXPathItem usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) { 50 | TorrentItem *item = [[TorrentItem alloc] init]; 51 | NSString *dateString = [[element firstChildWithTag:pubDateKey] stringValue]; 52 | NSString *pubDate = [[DMHYTool tool] formatedDateStringFromDMHYDateString:dateString]; 53 | item.pubDate = pubDate ? pubDate : @""; 54 | item.title = [[element firstChildWithTag:titleKey] stringValue]; 55 | item.link = [NSURL URLWithString:[[element firstChildWithTag:linkKey] stringValue]]; 56 | NSString *author = [[element firstChildWithTag:authorKey] stringValue]; 57 | item.author = author ? author : @""; 58 | NSString *magnetXPath = [NSString stringWithFormat:@"//item[%lu]//enclosure/@url", (idx+1)]; 59 | NSString *magStr = [[element firstChildWithXPath:magnetXPath] stringValue]; 60 | item.magnet = [NSURL URLWithString:magStr]; 61 | [torrents addObject:item]; 62 | }]; 63 | _successBlock(torrents); 64 | } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 65 | _failureBlock(error); 66 | }]; 67 | } 68 | 69 | - (void)fetchFromSite:(DMHYSite *)site queryKeyword:(DMHYKeyword *)keyword fetchedNew:(DMHYXMLDataFetchedNewTorrentBlock)block completion:(DMHYXMLDataFetchCompletionBlock)completionHandler failure:(DMHYXMLDataFetchFailureBlock)failureHandler { 70 | NSString *url = [[NSString stringWithFormat:site.searchURL, keyword.keyword] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; 71 | NSLog(@"AutoDL URL %@", url); 72 | AFHTTPSessionManager *manager = [AFHTTPSessionManager manager]; 73 | manager.responseSerializer = [AFXMLDocumentResponseSerializer serializer]; 74 | manager.responseSerializer.acceptableContentTypes = self.accepableContentTypes; 75 | [manager GET:url parameters:nil progress:nil success:^(NSURLSessionDataTask * _Nonnull task, id _Nullable responseObject) { 76 | ONOXMLDocument *xmlDoc = [ONOXMLDocument XMLDocumentWithString:[responseObject description] encoding:NSUTF8StringEncoding error:nil]; 77 | [xmlDoc enumerateElementsWithXPath:kXPathTorrentItem usingBlock:^(ONOXMLElement *element, NSUInteger idx, BOOL *stop) { 78 | NSString *title = [[element firstChildWithTag:@"title"] stringValue]; 79 | /* 80 | if (self.dontDownloadCollection) { 81 | if ([title containsString:@"合集"] || 82 | [title containsString:@"全集"]) { 83 | return; 84 | } 85 | } 86 | NSString *fliter = [[NSUserDefaults standardUserDefaults] stringForKey:FliterKeywordKey]; 87 | 88 | if (![fliter isEqualToString:@""] && (fliter.length != 0)) { 89 | __block NSMutableString *containFliterResult = [NSMutableString new]; 90 | NSArray *flites = [fliter componentsSeparatedByString:@" "]; 91 | [flites enumerateObjectsUsingBlock:^(id _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 92 | if ([title containsString:obj]) { 93 | [containFliterResult appendString:@"1"]; 94 | } else { 95 | [containFliterResult appendString:@"0"]; 96 | } 97 | }]; 98 | if ([containFliterResult containsString:@"0"]) { 99 | return ; 100 | } 101 | } 102 | */ 103 | NSFetchRequest *requestExistTorrent = [NSFetchRequest fetchRequestWithEntityName:@"Torrent"]; 104 | requestExistTorrent.predicate = [NSPredicate predicateWithFormat:@"keyword == %@ AND title == %@",keyword ,title]; 105 | NSArray *existsTorrents = [self.managedObjectContext executeFetchRequest:requestExistTorrent 106 | error:NULL]; 107 | if (!existsTorrents.count) { 108 | // NSLog(@"Didn't exist %@",title); 109 | DMHYTorrent *item = [NSEntityDescription insertNewObjectForEntityForName:@"Torrent" 110 | inManagedObjectContext:self.managedObjectContext]; 111 | NSString *dateString = [[element firstChildWithTag:@"pubDate"] stringValue]; 112 | item.pubDate = [[DMHYTool tool] formatedDateFromDMHYDateString:dateString]; 113 | item.title = [[element firstChildWithTag:@"title"] stringValue]; 114 | item.link = [[element firstChildWithTag:@"link"] stringValue]; 115 | item.author = [[element firstChildWithTag:@"author"] stringValue]; 116 | NSString *magnetXPath = [NSString stringWithFormat:@"//item[%lu]//enclosure/@url", (idx+1)]; 117 | NSString *magStr = [[element firstChildWithXPath:magnetXPath] stringValue]; 118 | item.magnet = magStr; 119 | item.isNewTorrent = @YES; 120 | item.isDownloaded = @NO; 121 | item.keyword = keyword; 122 | // NSLog(@"[New %@]",item.title); 123 | block(item); 124 | } else { 125 | // NSLog(@"Exist %@", title); 126 | *stop = 1; 127 | } 128 | }]; 129 | // NSLog(@"XML Document Enumerate Completed inform completion block"); 130 | completionHandler(); 131 | } failure:^(NSURLSessionDataTask * _Nullable task, NSError * _Nonnull error) { 132 | failureHandler(error); 133 | }]; 134 | } 135 | 136 | - (NSManagedObjectContext *)managedObjectContext { 137 | if (!_managedObjectContext) { 138 | _managedObjectContext = [[DMHYCoreDataStackManager sharedManager] managedObjectContext]; 139 | } 140 | return _managedObjectContext; 141 | } 142 | 143 | - (NSSet *)accepableContentTypes { 144 | if (!_accepableContentTypes) { 145 | _accepableContentTypes = [NSSet setWithObjects:@"application/rss+xml",@"text/xml",@"application/xml", nil]; 146 | } 147 | return _accepableContentTypes; 148 | } 149 | 150 | @end 151 | -------------------------------------------------------------------------------- /DMHY/FileManagerViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileManagerViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/25/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FileManagerViewController : NSViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/FileManagerViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileManagerViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/25/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "FileManagerViewController.h" 10 | #import "DMHYAPI.h" 11 | #import "PreferenceController.h" 12 | #import "AppDelegate.h" 13 | #import "NSTableView+ContextMenu.h" 14 | #import "FileItem.h" 15 | #import "FileTableView.h" 16 | #import "CDEvent.h" 17 | #import "CDEvents.h" 18 | #import 19 | 20 | @import Quartz; 21 | 22 | // QuickLook code from https://developer.apple.com/library/mac/samplecode/QuickLookDownloader/ 23 | 24 | @interface FileItem (QLPreviewItem) 25 | 26 | @end 27 | 28 | @implementation FileItem (QLPreviewItem) 29 | 30 | - (NSURL *)previewItemURL { 31 | return self.url; 32 | } 33 | 34 | - (NSString *)previewItemTitle { 35 | return self.fileName; 36 | } 37 | 38 | @end 39 | 40 | @interface FileManagerViewController () 41 | 42 | @property (weak) IBOutlet FileTableView *tableView; 43 | @property (weak) IBOutlet NSTextField *infoTextField; 44 | 45 | @property (strong) QLPreviewPanel *previewPanel; 46 | 47 | @property (nonatomic, strong) NSMutableArray *files; 48 | @property (nonatomic, strong) NSTimer *timer; 49 | 50 | @property (nonatomic, strong) NSURL *fileWatchURL; 51 | 52 | @property (nonatomic, strong) NSFileManager *fileManager; 53 | @property (nonatomic, strong) NSDateFormatter *formater; 54 | 55 | @property (nonatomic, strong) CDEvents *events; 56 | 57 | @end 58 | 59 | @implementation FileManagerViewController 60 | 61 | - (void)viewDidLoad { 62 | [super viewDidLoad]; 63 | [self observeNotification]; 64 | if (self.fileWatchURL) { 65 | [self setupFileData]; 66 | [self setupFileWatcher]; 67 | } else { 68 | self.infoTextField.stringValue = @"请在设定里设置文件监控路径 >_<"; 69 | } 70 | [self setupTableViewDoubleAction]; 71 | self.title = NSLocalizedString(@"File Manager", @""); 72 | } 73 | 74 | - (void)setupTableViewDoubleAction { 75 | self.tableView.doubleAction = @selector(openLocalFile:); 76 | } 77 | 78 | - (void)setupFileWatcher { 79 | CDEventsEventStreamCreationFlags creationFlags = kCDEventsDefaultEventStreamFlags; 80 | 81 | creationFlags |= kFSEventStreamCreateFlagFileEvents; 82 | 83 | 84 | _events = [[CDEvents alloc] initWithURLs:@[self.fileWatchURL] 85 | delegate:self 86 | onRunLoop:[NSRunLoop mainRunLoop] 87 | sinceEventIdentifier:kCDEventsSinceEventNow 88 | notificationLantency:CD_EVENTS_DEFAULT_NOTIFICATION_LATENCY 89 | ignoreEventsFromSubDirs:CD_EVENTS_DEFAULT_IGNORE_EVENT_FROM_SUB_DIRS 90 | excludeURLs:0 91 | streamCreationFlags:creationFlags]; 92 | } 93 | 94 | - (void)setupTableViewStyle { 95 | self.tableView.rowSizeStyle = [self preferedRowSizeStyle]; 96 | self.tableView.usesAlternatingRowBackgroundColors = YES; 97 | self.tableView.columnAutoresizingStyle = NSTableViewUniformColumnAutoresizingStyle; 98 | [self.tableView sizeLastColumnToFit]; 99 | } 100 | 101 | - (NSTableViewRowSizeStyle )preferedRowSizeStyle { 102 | NSInteger rowStyle = [PreferenceController viewPreferenceTableViewRowStyle]; 103 | switch (rowStyle) { 104 | case 0: 105 | return NSTableViewRowSizeStyleSmall; 106 | case 1: 107 | return NSTableViewRowSizeStyleMedium; 108 | case 2: 109 | return NSTableViewRowSizeStyleLarge; 110 | default: 111 | break; 112 | } 113 | return NSTableViewRowSizeStyleSmall; 114 | } 115 | 116 | - (NSURL *)fileWatchURL { 117 | if (!_fileWatchURL) { 118 | _fileWatchURL = [PreferenceController fileWatchPath]; 119 | [_fileWatchURL startAccessingSecurityScopedResource]; 120 | } 121 | return _fileWatchURL; 122 | } 123 | 124 | //const UInt32 DMHYFileEventFlagFileChanged = 67584; 125 | typedef NS_ENUM(UInt32, DMHYFileEventFlag) { 126 | DMHYFileEventFlagFileMoved = 128256, 127 | DMHYFileEventFlagFileChanged = 67584, 128 | DMHYFileEventFlagFileDeleted = 70656, 129 | DMHYFileEventFlagDirectoryChanged = 163840 130 | }; 131 | 132 | - (void)URLWatcher:(CDEvents *)urlWatcher eventOccurred:(CDEvent *)event { 133 | if (event.flags == DMHYFileEventFlagFileChanged || 134 | event.flags == DMHYFileEventFlagFileMoved || 135 | event.flags == DMHYFileEventFlagDirectoryChanged) { 136 | [self setupFileData]; 137 | } 138 | 139 | } 140 | 141 | #pragma mark - File Watch 142 | 143 | - (void)setupFileData { 144 | // NSLog(@"Setup File Data"); 145 | dispatch_queue_t file_queue = dispatch_queue_create("file_watch_queue", 0); 146 | dispatch_async(file_queue, ^{ 147 | // NSLog(@"File Manager %p File Watch URL %p",self.fileManager, self.fileWatchURL); 148 | if (!self.fileWatchURL) { //Stop when use didn't select directory. 149 | // NSLog(@"Not set watch directory."); 150 | return ; 151 | } 152 | 153 | NSDirectoryEnumerator *enumerator = [self.fileManager enumeratorAtURL:self.fileWatchURL 154 | includingPropertiesForKeys:@[NSURLNameKey, NSURLIsDirectoryKey] 155 | options:NSDirectoryEnumerationSkipsHiddenFiles 156 | errorHandler:^BOOL(NSURL *url, NSError *error) 157 | { 158 | if (error) { 159 | NSLog(@"[Error] %@ (%@)", error, url); 160 | return NO; 161 | } 162 | 163 | return YES; 164 | }]; 165 | 166 | NSMutableArray *mutableFileURLs = [NSMutableArray array]; 167 | NSMutableArray *mutableFileNames = [NSMutableArray array]; 168 | for (NSURL *fileURL in enumerator) { 169 | NSString *filename; 170 | [fileURL getResourceValue:&filename forKey:NSURLNameKey error:nil]; 171 | 172 | NSNumber *isDirectory; 173 | [fileURL getResourceValue:&isDirectory forKey:NSURLIsDirectoryKey error:nil]; 174 | // Skip directories with '_' prefix, for example 175 | if ([filename hasPrefix:@"_"] && [isDirectory boolValue]) { 176 | [enumerator skipDescendants]; 177 | continue; 178 | } 179 | 180 | if (![isDirectory boolValue]) { 181 | [mutableFileURLs addObject:fileURL]; 182 | [mutableFileNames addObject:filename]; 183 | } 184 | } 185 | 186 | __block NSMutableArray *tempArr = [NSMutableArray new]; 187 | __block NSDate *today = [[NSDate alloc] initWithTimeIntervalSinceNow:-60*60*24*7]; 188 | [mutableFileURLs enumerateObjectsUsingBlock:^(id _Nonnull filePath, NSUInteger idx, BOOL * _Nonnull stop) { 189 | NSURL *url = filePath; 190 | NSString *extenstion = url.pathExtension; 191 | if ([extenstion isEqualToString:@"mp4"] || 192 | [extenstion isEqualToString:@"mkv"] ) { 193 | if ([self.fileManager fileExistsAtPath:url.path]) { 194 | NSDictionary *attributes = [self.fileManager attributesOfItemAtPath:url.path error:nil]; 195 | NSDate *modi = attributes[NSFileModificationDate]; 196 | if ([modi compare:today] == NSOrderedDescending) { 197 | FileItem *item = [[FileItem alloc] initWithURL:url 198 | fileName:mutableFileNames[idx] 199 | modifyDate:modi]; 200 | [tempArr addObject:item]; 201 | } 202 | } 203 | } 204 | }]; 205 | // Didn't stop accessing in order to preview video 206 | // [fileWatchURL stopAccessingSecurityScopedResource]; 207 | NSSortDescriptor *descriptor = [NSSortDescriptor sortDescriptorWithKey:@"modifyDate" ascending:NO]; 208 | self.files = [[tempArr sortedArrayUsingDescriptors:@[descriptor]] mutableCopy]; 209 | dispatch_async(dispatch_get_main_queue(), ^{ 210 | [self.tableView reloadData]; 211 | [self.previewPanel reloadData]; 212 | [self setupTableViewStyle]; 213 | [self.tableView setNeedsDisplay:YES]; 214 | NSDate *time = [NSDate new]; 215 | self.infoTextField.stringValue = [NSString stringWithFormat:@"文件列表载入时间:%@",[[DMHYTool tool] infoDateStringFromDate:time]]; 216 | }); 217 | }); 218 | 219 | } 220 | 221 | #pragma mark - Table View DataSource 222 | 223 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { 224 | return self.files.count; 225 | } 226 | 227 | - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { 228 | NSString *identifier = tableColumn.identifier; 229 | FileItem *file = self.files[row]; 230 | if ([identifier isEqualToString:@"modifyDateCell"]) { 231 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"modifyDateCell" owner:self]; 232 | NSDate *modi = file.modifyDate; 233 | NSString *cDate = [[DMHYTool tool] stringFromSavedDate:modi]; 234 | cellView.textField.stringValue = cDate; 235 | return cellView; 236 | } 237 | if ([identifier isEqualToString:@"fileNameCell"]) { 238 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"fileNameCell" owner:self]; 239 | cellView.textField.stringValue = file.fileName; 240 | return cellView; 241 | } 242 | return nil; 243 | } 244 | 245 | - (NSDateFormatter *)formater { 246 | if (!_formater) { 247 | _formater = [NSDateFormatter new]; 248 | _formater.locale = [[NSLocale alloc] initWithLocaleIdentifier:@"zh_CN"]; 249 | _formater.dateFormat = @"EEE HH:mm:ss yy-MM-dd"; 250 | } 251 | return _formater; 252 | } 253 | 254 | #pragma mark - Context Menu Delegate 255 | 256 | - (NSMenu *)tableView:(NSTableView *)aTableView menuForRows:(NSIndexSet *)rows { 257 | NSMenu *rightClickMenu = [[NSMenu alloc] initWithTitle:@""]; 258 | NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"打开" 259 | action:@selector(openLocalFile:) 260 | keyEquivalent:@""]; 261 | NSMenuItem *downloadItem = [[NSMenuItem alloc] initWithTitle:@"在 Finder 中显示" 262 | action:@selector(locateInFinder:) 263 | keyEquivalent:@""]; 264 | [rightClickMenu addItem:openItem]; 265 | [rightClickMenu addItem:downloadItem]; 266 | return rightClickMenu; 267 | } 268 | 269 | #pragma mark - Utils 270 | 271 | - (void)locateInFinder:(id) sender { 272 | NSInteger selectRow = self.tableView.selectedRow; 273 | FileItem *file = self.files[selectRow]; 274 | [[NSWorkspace sharedWorkspace] activateFileViewerSelectingURLs:@[file.url]]; 275 | } 276 | 277 | - (void)openLocalFile:(id)sender { 278 | NSInteger selectRow = self.tableView.selectedRow; 279 | if (selectRow < 0) { 280 | return; 281 | } 282 | FileItem *file = self.files[selectRow]; 283 | [[NSWorkspace sharedWorkspace] openURL:file.url]; 284 | } 285 | 286 | - (void)observeNotification { 287 | [DMHYNotification addObserver:self selector:@selector(handleThemeChanged) name:DMHYThemeChangedNotification]; 288 | [DMHYNotification addObserver:self selector:@selector(handleFileWatchPathChanged) name:DMHYFileWatchPathChangedNotification]; 289 | [DMHYNotification addObserver:self selector:@selector(handleMainTableViewRowStyleChanged) name:DMHYMainTableViewRowStyleChangedNotification]; 290 | } 291 | 292 | - (void)handleFileWatchPathChanged { 293 | self.files = nil; 294 | self.fileWatchURL = nil; 295 | [self setupFileData]; 296 | } 297 | 298 | - (void)handleMainTableViewRowStyleChanged { 299 | [self setupTableViewStyle]; 300 | [self.tableView setNeedsDisplay:YES]; 301 | } 302 | 303 | - (void)handleThemeChanged { 304 | [self.view setNeedsDisplay:YES]; 305 | } 306 | 307 | - (NSMutableArray *)files { 308 | if (!_files) { 309 | _files = [NSMutableArray new]; 310 | } 311 | return _files; 312 | } 313 | 314 | - (NSFileManager *)fileManager { 315 | if (!_fileManager) { 316 | _fileManager = [NSFileManager defaultManager]; 317 | } 318 | return _fileManager; 319 | } 320 | 321 | #pragma mark - Quick Look panel support 322 | 323 | - (BOOL)acceptsPreviewPanelControl:(QLPreviewPanel *)panel { 324 | return YES; 325 | } 326 | 327 | - (void)beginPreviewPanelControl:(QLPreviewPanel *)panel { 328 | _previewPanel = panel; 329 | panel.delegate = self; 330 | panel.dataSource = self; 331 | } 332 | 333 | - (void)endPreviewPanelControl:(QLPreviewPanel *)panel { 334 | _previewPanel = nil; 335 | } 336 | 337 | #pragma mark - QLPreviewPanelDataSource 338 | 339 | - (NSInteger)numberOfPreviewItemsInPreviewPanel:(QLPreviewPanel *)panel { 340 | return self.files.count; 341 | } 342 | 343 | - (id )previewPanel:(QLPreviewPanel *)panel previewItemAtIndex:(NSInteger)index { 344 | NSInteger selectRow = self.tableView.selectedRow; 345 | return self.files[selectRow]; 346 | } 347 | 348 | #pragma mark - QLPreviewPanelDelegate 349 | 350 | - (BOOL)previewPanel:(QLPreviewPanel *)panel handleEvent:(NSEvent *)event { 351 | // redirect all key down events to the table view 352 | if ([event type] == NSKeyDown) { 353 | [self.tableView keyDown:event]; 354 | return YES; 355 | } 356 | return NO; 357 | } 358 | 359 | // This delegate method provides the rect on screen from which the panel will zoom. 360 | - (NSRect)previewPanel:(QLPreviewPanel *)panel sourceFrameOnScreenForPreviewItem:(id )item { 361 | NSInteger index = [self.files indexOfObject:item]; 362 | if (index == NSNotFound) { 363 | return NSZeroRect; 364 | } 365 | 366 | NSRect iconRect = [self.tableView frameOfCellAtColumn:0 row:index]; 367 | 368 | // check that the icon rect is visible on screen 369 | NSRect visibleRect = [self.tableView visibleRect]; 370 | 371 | if (!NSIntersectsRect(visibleRect, iconRect)) { 372 | return NSZeroRect; 373 | } 374 | 375 | // convert icon rect to screen coordinates 376 | iconRect = [self.tableView convertRectToBacking:iconRect]; 377 | NSRect test = [[self.tableView window] convertRectToScreen:iconRect]; 378 | iconRect.origin = test.origin; 379 | 380 | return iconRect; 381 | } 382 | 383 | @end 384 | -------------------------------------------------------------------------------- /DMHY/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | APPL 17 | CFBundleShortVersionString 18 | $(MARKETING_VERSION) 19 | CFBundleSignature 20 | ???? 21 | CFBundleVersion 22 | $(CURRENT_PROJECT_VERSION) 23 | LSApplicationCategoryType 24 | public.app-category.utilities 25 | LSMinimumSystemVersion 26 | $(MACOSX_DEPLOYMENT_TARGET) 27 | NSAppTransportSecurity 28 | 29 | NSAllowsArbitraryLoads 30 | 31 | 32 | NSHumanReadableCopyright 33 | Copyright © 2016-2021 yaqinking 34 | All rights reserved. 35 | NSMainStoryboardFile 36 | Main 37 | NSPrincipalClass 38 | NSApplication 39 | 40 | 41 | -------------------------------------------------------------------------------- /DMHY/MASPreferencesViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // Any controller providing preference pane view must support this protocol 3 | // 4 | 5 | #import 6 | 7 | @protocol MASPreferencesViewController 8 | 9 | @optional 10 | 11 | - (void)viewWillAppear; 12 | - (void)viewDidDisappear; 13 | - (NSView *)initialKeyView; 14 | 15 | @property (nonatomic, readonly) BOOL hasResizableWidth; 16 | @property (nonatomic, readonly) BOOL hasResizableHeight; 17 | 18 | @required 19 | 20 | @property (nonatomic, readonly) NSString *identifier; 21 | @property (nonatomic, readonly) NSImage *toolbarItemImage; 22 | @property (nonatomic, readonly) NSString *toolbarItemLabel; 23 | 24 | @end 25 | -------------------------------------------------------------------------------- /DMHY/MASPreferencesWindow.xib: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 1080 5 | 12E55 6 | 3084 7 | 1187.39 8 | 626.00 9 | 10 | com.apple.InterfaceBuilder.CocoaPlugin 11 | 3084 12 | 13 | 14 | YES 15 | NSCustomObject 16 | NSToolbar 17 | NSView 18 | NSWindowTemplate 19 | 20 | 21 | YES 22 | com.apple.InterfaceBuilder.CocoaPlugin 23 | 24 | 25 | PluginDependencyRecalculationVersion 26 | 27 | 28 | 29 | YES 30 | 31 | MASPreferencesWindowController 32 | 33 | 34 | FirstResponder 35 | 36 | 37 | NSApplication 38 | 39 | 40 | 11 41 | 2 42 | {{540, 400}, {360, 270}} 43 | 1618478080 44 | 45 | NSWindow 46 | 47 | 48 | A3419266-C6CB-4FAA-AB63-B91B70C196EA 49 | 50 | 51 | YES 52 | YES 53 | NO 54 | NO 55 | 1 56 | 1 57 | 58 | YES 59 | 60 | YES 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 256 72 | {360, 270} 73 | 74 | 75 | 76 | 77 | {{0, 0}, {2560, 1418}} 78 | {10000000000000, 10000000000000} 79 | 80 | YES 81 | 82 | 83 | 84 | 85 | YES 86 | 87 | 88 | window 89 | 90 | 91 | 92 | 3 93 | 94 | 95 | 96 | toolbar 97 | 98 | 99 | 100 | 23 101 | 102 | 103 | 104 | delegate 105 | 106 | 107 | 108 | 20 109 | 110 | 111 | 112 | delegate 113 | 114 | 115 | 116 | 22 117 | 118 | 119 | 120 | 121 | YES 122 | 123 | 0 124 | 125 | 126 | 127 | 128 | 129 | -2 130 | 131 | 132 | File's Owner 133 | 134 | 135 | -1 136 | 137 | 138 | First Responder 139 | 140 | 141 | -3 142 | 143 | 144 | Application 145 | 146 | 147 | 1 148 | 149 | 150 | YES 151 | 152 | 153 | 154 | 155 | 156 | 157 | 2 158 | 159 | 160 | 161 | 162 | 4 163 | 164 | 165 | YES 166 | 167 | 168 | 169 | 170 | 171 | 172 | YES 173 | 174 | YES 175 | -1.IBPluginDependency 176 | -2.IBPluginDependency 177 | -3.IBPluginDependency 178 | 1.IBNSWindowAutoPositionCentersHorizontal 179 | 1.IBNSWindowAutoPositionCentersVertical 180 | 1.IBPluginDependency 181 | 1.IBWindowTemplateEditedContentRect 182 | 1.NSWindowTemplate.visibleAtLaunch 183 | 2.IBPluginDependency 184 | 4.IBPluginDependency 185 | 186 | 187 | YES 188 | com.apple.InterfaceBuilder.CocoaPlugin 189 | com.apple.InterfaceBuilder.CocoaPlugin 190 | com.apple.InterfaceBuilder.CocoaPlugin 191 | 192 | 193 | com.apple.InterfaceBuilder.CocoaPlugin 194 | {{484, 402}, {360, 270}} 195 | 196 | com.apple.InterfaceBuilder.CocoaPlugin 197 | com.apple.InterfaceBuilder.CocoaPlugin 198 | 199 | 200 | 201 | YES 202 | 203 | 204 | 205 | 206 | 207 | YES 208 | 209 | 210 | 211 | 212 | 23 213 | 214 | 215 | 216 | YES 217 | 218 | MASPreferencesWindowController 219 | NSWindowController 220 | 221 | YES 222 | 223 | YES 224 | goNextTab: 225 | goPreviousTab: 226 | 227 | 228 | YES 229 | id 230 | id 231 | 232 | 233 | 234 | YES 235 | 236 | YES 237 | goNextTab: 238 | goPreviousTab: 239 | 240 | 241 | YES 242 | 243 | goNextTab: 244 | id 245 | 246 | 247 | goPreviousTab: 248 | id 249 | 250 | 251 | 252 | 253 | IBProjectSource 254 | ./Classes/MASPreferencesWindowController.h 255 | 256 | 257 | 258 | 259 | 0 260 | IBCocoaFramework 261 | 262 | com.apple.InterfaceBuilder.CocoaPlugin.InterfaceBuilder3 263 | 264 | 265 | YES 266 | 3 267 | 268 | 269 | -------------------------------------------------------------------------------- /DMHY/MASPreferencesWindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // You create an application Preferences window using code like this: 3 | // _preferencesWindowController = [[MASPreferencesWindowController alloc] initWithViewControllers:controllers 4 | // title:title] 5 | // 6 | // To open the Preferences window: 7 | // [_preferencesWindowController showWindow:sender] 8 | // 9 | 10 | #import "MASPreferencesViewController.h" 11 | 12 | extern NSString *const kMASPreferencesWindowControllerDidChangeViewNotification; 13 | 14 | __attribute__((__visibility__("default"))) 15 | #if MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_5 16 | @interface MASPreferencesWindowController : NSWindowController 17 | #else 18 | @interface MASPreferencesWindowController : NSWindowController 19 | #endif 20 | { 21 | @private 22 | NSMutableArray *_viewControllers; 23 | NSMutableDictionary *_minimumViewRects; 24 | NSString *_title; 25 | NSViewController *_selectedViewController; 26 | NSToolbar * __unsafe_unretained _toolbar; 27 | } 28 | 29 | @property (nonatomic, readonly) NSMutableArray *viewControllers; 30 | @property (nonatomic, readonly) NSUInteger indexOfSelectedController; 31 | @property (nonatomic, readonly, retain) NSViewController *selectedViewController; 32 | @property (nonatomic, readonly) NSString *title; 33 | @property (nonatomic, assign) IBOutlet NSToolbar *toolbar; 34 | 35 | - (id)initWithViewControllers:(NSArray *)viewControllers; 36 | - (id)initWithViewControllers:(NSArray *)viewControllers title:(NSString *)title; 37 | - (void)addViewController:(NSViewController *) viewController; 38 | 39 | - (void)selectControllerAtIndex:(NSUInteger)controllerIndex; 40 | - (void)selectControllerWithIdentifier:(NSString *)identifier; 41 | 42 | - (IBAction)goNextTab:(id)sender; 43 | - (IBAction)goPreviousTab:(id)sender; 44 | 45 | @end 46 | -------------------------------------------------------------------------------- /DMHY/MASPreferencesWindowController.m: -------------------------------------------------------------------------------- 1 | #import "MASPreferencesWindowController.h" 2 | 3 | NSString *const kMASPreferencesWindowControllerDidChangeViewNotification = @"MASPreferencesWindowControllerDidChangeViewNotification"; 4 | 5 | static NSString *const kMASPreferencesFrameTopLeftKey = @"MASPreferences Frame Top Left"; 6 | static NSString *const kMASPreferencesSelectedViewKey = @"MASPreferences Selected Identifier View"; 7 | 8 | static NSString * PreferencesKeyForViewBounds (NSString *identifier) 9 | { 10 | return [NSString stringWithFormat:@"MASPreferences %@ Frame", identifier]; 11 | } 12 | 13 | @interface MASPreferencesWindowController () // Private 14 | 15 | - (NSViewController *)viewControllerForIdentifier:(NSString *)identifier; 16 | 17 | @property (readonly) NSArray *toolbarItemIdentifiers; 18 | @property (nonatomic, retain) NSViewController *selectedViewController; 19 | 20 | @end 21 | 22 | #pragma mark - 23 | 24 | @implementation MASPreferencesWindowController 25 | 26 | @synthesize viewControllers = _viewControllers; 27 | @synthesize selectedViewController = _selectedViewController; 28 | @synthesize title = _title; 29 | @synthesize toolbar = _toolbar; 30 | #pragma mark - 31 | 32 | - (id)initWithViewControllers:(NSArray *)viewControllers 33 | { 34 | return [self initWithViewControllers:viewControllers title:nil]; 35 | } 36 | 37 | - (id)initWithViewControllers:(NSArray *)viewControllers title:(NSString *)title 38 | { 39 | NSString *nibPath = [[NSBundle bundleForClass:MASPreferencesWindowController.class] pathForResource:@"MASPreferencesWindow" ofType:@"nib"]; 40 | if ((self = [super initWithWindowNibPath:nibPath owner:self])) 41 | { 42 | _viewControllers = [NSMutableArray arrayWithArray: viewControllers]; 43 | #if !__has_feature(objc_arc) 44 | _viewControllers = [_viewControllers retain]; 45 | #endif 46 | _minimumViewRects = [[NSMutableDictionary alloc] init]; 47 | _title = [title copy]; 48 | } 49 | return self; 50 | } 51 | 52 | - (void)dealloc 53 | { 54 | [[NSNotificationCenter defaultCenter] removeObserver:self]; 55 | [[self window] setDelegate:nil]; 56 | for (NSToolbarItem *item in [self.toolbar items]) { 57 | item.target = nil; 58 | item.action = nil; 59 | } 60 | self.toolbar.delegate = nil; 61 | #if !__has_feature(objc_arc) 62 | [_viewControllers release]; 63 | [_selectedViewController release]; 64 | [_minimumViewRects release]; 65 | [_title release]; 66 | [super dealloc]; 67 | #endif 68 | } 69 | 70 | - (void)addViewController: (NSViewController *) viewController 71 | { 72 | [_viewControllers addObject: viewController]; 73 | [_toolbar insertItemWithItemIdentifier: [viewController identifier] atIndex: ([_viewControllers count] - 1)]; 74 | [_toolbar validateVisibleItems]; 75 | } 76 | 77 | #pragma mark - 78 | 79 | - (void)windowDidLoad 80 | { 81 | if ([self.title length] > 0) 82 | [[self window] setTitle:self.title]; 83 | 84 | if ([self.viewControllers count]) 85 | self.selectedViewController = [self viewControllerForIdentifier:[[NSUserDefaults standardUserDefaults] stringForKey:kMASPreferencesSelectedViewKey]] ?: [self firstViewController]; 86 | 87 | NSString *origin = [[NSUserDefaults standardUserDefaults] stringForKey:kMASPreferencesFrameTopLeftKey]; 88 | if (origin) 89 | [self.window setFrameTopLeftPoint:NSPointFromString(origin)]; 90 | 91 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidMove:) name:NSWindowDidMoveNotification object:self.window]; 92 | [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(windowDidResize:) name:NSWindowDidResizeNotification object:self.window]; 93 | } 94 | 95 | - (NSViewController *)firstViewController { 96 | for (id viewController in self.viewControllers) 97 | if ([viewController isKindOfClass:[NSViewController class]]) 98 | return viewController; 99 | 100 | return nil; 101 | } 102 | 103 | #pragma mark - 104 | #pragma mark NSWindowDelegate 105 | 106 | - (BOOL)windowShouldClose:(id __unused)sender 107 | { 108 | return !self.selectedViewController || [self.selectedViewController commitEditing]; 109 | } 110 | 111 | - (void)windowDidMove:(NSNotification* __unused)aNotification 112 | { 113 | [[NSUserDefaults standardUserDefaults] setObject:NSStringFromPoint(NSMakePoint(NSMinX([self.window frame]), NSMaxY([self.window frame]))) forKey:kMASPreferencesFrameTopLeftKey]; 114 | } 115 | 116 | - (void)windowDidResize:(NSNotification* __unused)aNotification 117 | { 118 | NSViewController *viewController = self.selectedViewController; 119 | if (viewController) 120 | [[NSUserDefaults standardUserDefaults] setObject:NSStringFromRect([viewController.view bounds]) forKey:PreferencesKeyForViewBounds(viewController.identifier)]; 121 | } 122 | 123 | #pragma mark - 124 | #pragma mark Accessors 125 | 126 | - (NSArray *)toolbarItemIdentifiers 127 | { 128 | NSMutableArray *identifiers = [NSMutableArray arrayWithCapacity:_viewControllers.count]; 129 | for (id viewController in _viewControllers) 130 | if (viewController == [NSNull null]) 131 | [identifiers addObject:NSToolbarFlexibleSpaceItemIdentifier]; 132 | else 133 | [identifiers addObject:[viewController identifier]]; 134 | return identifiers; 135 | } 136 | 137 | #pragma mark - 138 | 139 | - (NSUInteger)indexOfSelectedController 140 | { 141 | NSUInteger index = [self.toolbarItemIdentifiers indexOfObject:self.selectedViewController.identifier]; 142 | return index; 143 | } 144 | 145 | #pragma mark - 146 | #pragma mark NSToolbarDelegate 147 | 148 | - (NSArray *)toolbarAllowedItemIdentifiers:(NSToolbar * __unused)toolbar 149 | { 150 | NSArray *identifiers = self.toolbarItemIdentifiers; 151 | return identifiers; 152 | } 153 | 154 | - (NSArray *)toolbarDefaultItemIdentifiers:(NSToolbar * __unused)toolbar 155 | { 156 | NSArray *identifiers = self.toolbarItemIdentifiers; 157 | return identifiers; 158 | } 159 | 160 | - (NSArray *)toolbarSelectableItemIdentifiers:(NSToolbar * __unused)toolbar 161 | { 162 | NSArray *identifiers = self.toolbarItemIdentifiers; 163 | return identifiers; 164 | } 165 | 166 | - (NSToolbarItem *)toolbar:(NSToolbar * __unused)toolbar itemForItemIdentifier:(NSString *)itemIdentifier willBeInsertedIntoToolbar:(BOOL __unused)flag 167 | { 168 | NSToolbarItem *toolbarItem = [[NSToolbarItem alloc] initWithItemIdentifier:itemIdentifier]; 169 | NSArray *identifiers = self.toolbarItemIdentifiers; 170 | NSUInteger controllerIndex = [identifiers indexOfObject:itemIdentifier]; 171 | if (controllerIndex != NSNotFound) 172 | { 173 | id controller = [_viewControllers objectAtIndex:controllerIndex]; 174 | toolbarItem.image = controller.toolbarItemImage; 175 | toolbarItem.label = controller.toolbarItemLabel; 176 | toolbarItem.target = self; 177 | toolbarItem.action = @selector(toolbarItemDidClick:); 178 | } 179 | #if !__has_feature(objc_arc) 180 | [toolbarItem autorelease]; 181 | #endif 182 | return toolbarItem; 183 | } 184 | 185 | #pragma mark - 186 | #pragma mark Private methods 187 | 188 | - (NSViewController *)viewControllerForIdentifier:(NSString *)identifier 189 | { 190 | for (id viewController in self.viewControllers) { 191 | if (viewController == [NSNull null]) continue; 192 | if ([[viewController identifier] isEqualToString:identifier]) 193 | return viewController; 194 | } 195 | return nil; 196 | } 197 | 198 | #pragma mark - 199 | 200 | - (void)setSelectedViewController:(NSViewController *)controller 201 | { 202 | if (_selectedViewController == controller) 203 | return; 204 | 205 | if (_selectedViewController) 206 | { 207 | // Check if we can commit changes for old controller 208 | if (![_selectedViewController commitEditing]) 209 | { 210 | [[self.window toolbar] setSelectedItemIdentifier:_selectedViewController.identifier]; 211 | return; 212 | } 213 | 214 | #if __has_feature(objc_arc) 215 | [self.window setContentView:[[NSView alloc] init]]; 216 | #else 217 | [self.window setContentView:[[[NSView alloc] init] autorelease]]; 218 | #endif 219 | [_selectedViewController setNextResponder:nil]; 220 | if ([_selectedViewController respondsToSelector:@selector(viewDidDisappear)]) 221 | [_selectedViewController viewDidDisappear]; 222 | 223 | #if !__has_feature(objc_arc) 224 | [_selectedViewController release]; 225 | #endif 226 | _selectedViewController = nil; 227 | } 228 | 229 | if (!controller) 230 | return; 231 | 232 | // Retrieve the new window tile from the controller view 233 | if ([self.title length] == 0) 234 | { 235 | NSString *label = controller.toolbarItemLabel; 236 | self.window.title = label; 237 | } 238 | 239 | [[self.window toolbar] setSelectedItemIdentifier:controller.identifier]; 240 | 241 | // Record new selected controller in user defaults 242 | [[NSUserDefaults standardUserDefaults] setObject:controller.identifier forKey:kMASPreferencesSelectedViewKey]; 243 | 244 | NSView *controllerView = controller.view; 245 | 246 | // Retrieve current and minimum frame size for the view 247 | NSString *oldViewRectString = [[NSUserDefaults standardUserDefaults] stringForKey:PreferencesKeyForViewBounds(controller.identifier)]; 248 | NSString *minViewRectString = [_minimumViewRects objectForKey:controller.identifier]; 249 | if (!minViewRectString) 250 | [_minimumViewRects setObject:NSStringFromRect(controllerView.bounds) forKey:controller.identifier]; 251 | 252 | BOOL sizableWidth = ([controller respondsToSelector:@selector(hasResizableWidth)] 253 | ? controller.hasResizableWidth 254 | : controllerView.autoresizingMask & NSViewWidthSizable); 255 | BOOL sizableHeight = ([controller respondsToSelector:@selector(hasResizableHeight)] 256 | ? controller.hasResizableHeight 257 | : controllerView.autoresizingMask & NSViewHeightSizable); 258 | 259 | NSRect oldViewRect = oldViewRectString ? NSRectFromString(oldViewRectString) : controllerView.bounds; 260 | NSRect minViewRect = minViewRectString ? NSRectFromString(minViewRectString) : controllerView.bounds; 261 | oldViewRect.size.width = NSWidth(oldViewRect) < NSWidth(minViewRect) || !sizableWidth ? NSWidth(minViewRect) : NSWidth(oldViewRect); 262 | oldViewRect.size.height = NSHeight(oldViewRect) < NSHeight(minViewRect) || !sizableHeight ? NSHeight(minViewRect) : NSHeight(oldViewRect); 263 | 264 | [controllerView setFrame:oldViewRect]; 265 | 266 | // Calculate new window size and position 267 | NSRect oldFrame = [self.window frame]; 268 | NSRect newFrame = [self.window frameRectForContentRect:oldViewRect]; 269 | newFrame = NSOffsetRect(newFrame, NSMinX(oldFrame), NSMaxY(oldFrame) - NSMaxY(newFrame)); 270 | 271 | // Setup min/max sizes and show/hide resize indicator 272 | [self.window setContentMinSize:minViewRect.size]; 273 | [self.window setContentMaxSize:NSMakeSize(sizableWidth ? CGFLOAT_MAX : NSWidth(oldViewRect), sizableHeight ? CGFLOAT_MAX : NSHeight(oldViewRect))]; 274 | [self.window setShowsResizeIndicator:sizableWidth || sizableHeight]; 275 | [[self.window standardWindowButton:NSWindowZoomButton] setEnabled:sizableWidth || sizableHeight]; 276 | 277 | [self.window setFrame:newFrame display:YES animate:[self.window isVisible]]; 278 | 279 | #if __has_feature(objc_arc) 280 | _selectedViewController = controller; 281 | #else 282 | _selectedViewController = [controller retain]; 283 | #endif 284 | // In OSX 10.10, setContentView below calls viewWillAppear. We still want to call viewWillAppear on < 10.10, 285 | // so the check below avoids calling viewWillAppear twice on 10.10. 286 | // See https://github.com/shpakovski/MASPreferences/issues/32 for more info. 287 | if (![NSViewController instancesRespondToSelector:@selector(viewWillAppear)]) 288 | if ([controller respondsToSelector:@selector(viewWillAppear)]) 289 | [controller viewWillAppear]; 290 | 291 | [self.window setContentView:controllerView]; 292 | [self.window recalculateKeyViewLoop]; 293 | if ([self.window firstResponder] == self.window) { 294 | if ([controller respondsToSelector:@selector(initialKeyView)]) 295 | [self.window makeFirstResponder:[controller initialKeyView]]; 296 | else 297 | [self.window selectKeyViewFollowingView:controllerView]; 298 | } 299 | 300 | // Insert view controller into responder chain on 10.9 and earlier 301 | if (controllerView.nextResponder != controller) { 302 | controller.nextResponder = controllerView.nextResponder; 303 | controllerView.nextResponder = controller; 304 | } 305 | 306 | [[NSNotificationCenter defaultCenter] postNotificationName:kMASPreferencesWindowControllerDidChangeViewNotification object:self]; 307 | } 308 | 309 | - (void)toolbarItemDidClick:(id)sender 310 | { 311 | if ([sender respondsToSelector:@selector(itemIdentifier)]) 312 | self.selectedViewController = [self viewControllerForIdentifier:[sender itemIdentifier]]; 313 | } 314 | 315 | #pragma mark - 316 | #pragma mark Public methods 317 | 318 | - (void)selectControllerAtIndex:(NSUInteger)controllerIndex 319 | { 320 | if (NSLocationInRange(controllerIndex, NSMakeRange(0, _viewControllers.count))) 321 | self.selectedViewController = [self.viewControllers objectAtIndex:controllerIndex]; 322 | } 323 | 324 | - (void)selectControllerWithIdentifier:(NSString *)identifier 325 | { 326 | self.selectedViewController = [self viewControllerForIdentifier:identifier]; 327 | } 328 | 329 | #pragma mark - 330 | #pragma mark Actions 331 | 332 | - (IBAction)goNextTab:(id __unused)sender 333 | { 334 | NSUInteger selectedIndex = self.indexOfSelectedController; 335 | NSUInteger numberOfControllers = [_viewControllers count]; 336 | 337 | do { selectedIndex = (selectedIndex + 1) % numberOfControllers; } 338 | while ([_viewControllers objectAtIndex:selectedIndex] == [NSNull null]); 339 | 340 | [self selectControllerAtIndex:selectedIndex]; 341 | } 342 | 343 | - (IBAction)goPreviousTab:(id __unused)sender 344 | { 345 | NSUInteger selectedIndex = self.indexOfSelectedController; 346 | NSUInteger numberOfControllers = [_viewControllers count]; 347 | 348 | do { selectedIndex = (selectedIndex + numberOfControllers - 1) % numberOfControllers; } 349 | while ([_viewControllers objectAtIndex:selectedIndex] == [NSNull null]); 350 | 351 | [self selectControllerAtIndex:selectedIndex]; 352 | } 353 | 354 | @end 355 | -------------------------------------------------------------------------------- /DMHY/MainSplitViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // MainSplitViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/19/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface MainSplitViewController : NSSplitViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/MainSplitViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // MainSplitViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/19/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "MainSplitViewController.h" 10 | #import "SideViewController.h" 11 | 12 | @interface MainSplitViewController () 13 | 14 | @property (nonatomic, assign) BOOL isShowKeywordManager; 15 | 16 | @end 17 | 18 | @implementation MainSplitViewController 19 | 20 | - (void)viewDidLoad { 21 | 22 | [super viewDidLoad]; 23 | [self setupMenuItems]; 24 | } 25 | - (void)setupMenuItems { 26 | NSMenu *viewSubMenu = [self viewSubMenu]; 27 | NSMenuItem *toggleKeywordManagerMenuItem = [[NSMenuItem alloc] initWithTitle:@"隐藏关键字管理区域" 28 | action:@selector(toggleKeywordManager:) 29 | keyEquivalent:@""]; 30 | self.isShowKeywordManager = NO; 31 | [viewSubMenu addItem:toggleKeywordManagerMenuItem]; 32 | } 33 | 34 | - (void)toggleKeywordManager:(id) sender { 35 | NSSplitViewItem *item = self.splitViewItems[0]; 36 | item.collapsed = !item.collapsed; 37 | self.isShowKeywordManager = !self.isShowKeywordManager; 38 | NSMenu *viewSubMenu = [self viewSubMenu]; 39 | NSMenuItem *toggleKeywordManagerMenuItem = [viewSubMenu itemAtIndex:3]; 40 | toggleKeywordManagerMenuItem.title = self.isShowKeywordManager ? @"显示关键字管理区域" : @"隐藏关键字管理区域"; 41 | } 42 | 43 | - (NSMenu *)viewSubMenu { 44 | NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu]; 45 | NSMenuItem *viewMenuItem = [mainMenu itemWithTitle:@"View"]; 46 | return [viewMenuItem submenu]; 47 | } 48 | 49 | /* 50 | Always show divider. 51 | */ 52 | - (BOOL)splitView:(NSSplitView *)splitView shouldHideDividerAtIndex:(NSInteger)dividerIndex { 53 | [super splitView:splitView shouldHideDividerAtIndex:dividerIndex]; 54 | return NO; 55 | } 56 | 57 | 58 | @end 59 | -------------------------------------------------------------------------------- /DMHY/Model/Bangumi.h: -------------------------------------------------------------------------------- 1 | // 2 | // Bangumi.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/20/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface Bangumi : NSObject 12 | 13 | @property (nonatomic, strong) NSString *titleCN; 14 | @property (nonatomic, strong) NSString *titleCNFull; 15 | @property (nonatomic, strong) NSString *titleJP; 16 | @property (nonatomic, strong) NSString *titleEN; 17 | @property (nonatomic, strong) NSString *weekDayJP; 18 | @property (nonatomic, strong) NSString *weekDayCN; 19 | @property (nonatomic, strong) NSString *timeJP; 20 | @property (nonatomic, strong) NSString *timeCN; 21 | @property (nonatomic, strong) NSString *showDate; 22 | @property (nonatomic, strong) NSString *officalSite; 23 | @property (nonatomic, strong) NSString *subGroup; 24 | 25 | @property (nonatomic, assign, getter=isNewBgm) BOOL newBgm; 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /DMHY/Model/Bangumi.m: -------------------------------------------------------------------------------- 1 | // 2 | // Bangumi.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/20/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "Bangumi.h" 10 | 11 | @implementation Bangumi 12 | 13 | - (NSString *)description { 14 | return [NSString stringWithFormat:@"Bangumi title %@ weekDayCN %@ timeCN %@ showDate %@", self.titleCN, self.weekDayCN, self.timeCN, self.showDate]; 15 | } 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYKeyword+CoreDataProperties.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYKeyword+CoreDataProperties.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu 9 | // to delete and recreate this implementation file for your updated model. 10 | // 11 | 12 | #import "DMHYKeyword.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface DMHYKeyword (CoreDataProperties) 17 | 18 | @property (nullable, nonatomic, retain) NSDate *createDate; 19 | @property (nullable, nonatomic, retain) NSNumber *isSubKeyword; 20 | @property (nullable, nonatomic, retain) NSString *keyword; 21 | @property (nullable, nonatomic, retain) NSSet *subKeywords; 22 | @property (nullable, nonatomic, retain) NSSet *torrents; 23 | @property (nullable, nonatomic, retain) DMHYSite *site; 24 | 25 | @end 26 | 27 | @interface DMHYKeyword (CoreDataGeneratedAccessors) 28 | 29 | - (void)addSubKeywordsObject:(DMHYKeyword *)value; 30 | - (void)removeSubKeywordsObject:(DMHYKeyword *)value; 31 | - (void)addSubKeywords:(NSSet *)values; 32 | - (void)removeSubKeywords:(NSSet *)values; 33 | 34 | - (void)addTorrentsObject:(DMHYTorrent *)value; 35 | - (void)removeTorrentsObject:(DMHYTorrent *)value; 36 | - (void)addTorrents:(NSSet *)values; 37 | - (void)removeTorrents:(NSSet *)values; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYKeyword+CoreDataProperties.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYKeyword+CoreDataProperties.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu 9 | // to delete and recreate this implementation file for your updated model. 10 | // 11 | 12 | #import "DMHYKeyword+CoreDataProperties.h" 13 | 14 | @implementation DMHYKeyword (CoreDataProperties) 15 | 16 | @dynamic createDate; 17 | @dynamic isSubKeyword; 18 | @dynamic keyword; 19 | @dynamic subKeywords; 20 | @dynamic torrents; 21 | @dynamic site; 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYKeyword.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYKeyword.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/23/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class DMHYTorrent; 13 | @class DMHYSite; 14 | 15 | NS_ASSUME_NONNULL_BEGIN 16 | 17 | @interface DMHYKeyword : NSManagedObject 18 | 19 | // Insert code here to declare functionality of your managed object subclass 20 | 21 | + (DMHYKeyword *)entityForKeywordName:(NSString *)name isSubKeyword:(NSNumber *)subKeyword inManagedObjectContext:(NSManagedObjectContext *)context; 22 | 23 | @end 24 | 25 | NS_ASSUME_NONNULL_END 26 | 27 | #import "DMHYKeyword+CoreDataProperties.h" 28 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYKeyword.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYKeyword.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/23/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYKeyword.h" 10 | #import "DMHYTorrent.h" 11 | #import "DMHYSite.h" 12 | 13 | @implementation DMHYKeyword 14 | 15 | // Insert code here to add functionality to your managed object subclass 16 | 17 | + (DMHYKeyword *)entityForKeywordName:(NSString *)name isSubKeyword:(NSNumber *)subKeyword inManagedObjectContext:(NSManagedObjectContext *)context { 18 | DMHYKeyword *entity = [NSEntityDescription insertNewObjectForEntityForName:@"Keyword" inManagedObjectContext:context]; 19 | entity.keyword = name; 20 | entity.createDate = [NSDate new]; 21 | entity.isSubKeyword = subKeyword; 22 | return entity; 23 | } 24 | 25 | @end 26 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYSite+CoreDataProperties.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYSite+CoreDataProperties.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu 9 | // to delete and recreate this implementation file for your updated model. 10 | // 11 | 12 | #import "DMHYSite.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface DMHYSite (CoreDataProperties) 17 | 18 | @property (nullable, nonatomic, retain) NSString *name; 19 | @property (nullable, nonatomic, retain) NSString *mainURL; 20 | @property (nullable, nonatomic, retain) NSString *searchURL; 21 | @property (nullable, nonatomic, retain) NSNumber *isAutoDownload; 22 | @property (nullable, nonatomic, retain) NSString *downloadType; 23 | @property (nullable, nonatomic, retain) NSNumber *isDownloadFin; 24 | @property (nullable, nonatomic, retain) NSNumber *isCurrentUse; 25 | @property (nullable, nonatomic, retain) NSNumber *isFliterSite; 26 | @property (nullable, nonatomic, retain) NSDate *createDate; 27 | @property (nullable, nonatomic, retain) NSString *responseType; 28 | @property (nullable, nonatomic, retain) NSSet *keywords; 29 | 30 | @end 31 | 32 | @interface DMHYSite (CoreDataGeneratedAccessors) 33 | 34 | - (void)addKeywordsObject:(DMHYKeyword *)value; 35 | - (void)removeKeywordsObject:(DMHYKeyword *)value; 36 | - (void)addKeywords:(NSSet *)values; 37 | - (void)removeKeywords:(NSSet *)values; 38 | 39 | @end 40 | 41 | NS_ASSUME_NONNULL_END 42 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYSite+CoreDataProperties.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYSite+CoreDataProperties.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu 9 | // to delete and recreate this implementation file for your updated model. 10 | // 11 | 12 | #import "DMHYSite+CoreDataProperties.h" 13 | 14 | @implementation DMHYSite (CoreDataProperties) 15 | 16 | @dynamic name; 17 | @dynamic mainURL; 18 | @dynamic searchURL; 19 | @dynamic isAutoDownload; 20 | @dynamic downloadType; 21 | @dynamic isDownloadFin; 22 | @dynamic isCurrentUse; 23 | @dynamic isFliterSite; 24 | @dynamic createDate; 25 | @dynamic responseType; 26 | @dynamic keywords; 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYSite.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYSite.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class DMHYKeyword; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface DMHYSite : NSManagedObject 17 | 18 | // Insert code here to declare functionality of your managed object subclass 19 | 20 | + (DMHYSite *)entityFormDictionary:(NSDictionary *)site inManagedObjectContext:(NSManagedObjectContext *)context; 21 | 22 | @end 23 | 24 | NS_ASSUME_NONNULL_END 25 | 26 | #import "DMHYSite+CoreDataProperties.h" 27 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYSite.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYSite.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/16/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYSite.h" 10 | #import "DMHYKeyword.h" 11 | #import "DMHYAPI.h" 12 | 13 | @implementation DMHYSite 14 | 15 | // Insert code here to add functionality to your managed object subclass 16 | 17 | + (DMHYSite *)entityFormDictionary:(NSDictionary *)site inManagedObjectContext:(NSManagedObjectContext *)context { 18 | DMHYSite *siteEntity = [NSEntityDescription insertNewObjectForEntityForName:DMHYSiteEntityKey inManagedObjectContext:context]; 19 | siteEntity.name = site[DMHYSiteNameKey]; 20 | siteEntity.mainURL = site[DMHYSiteMainURLKey]; 21 | siteEntity.searchURL = site[DMHYSiteSearchURLKey]; 22 | siteEntity.isFliterSite = site[DMHYSiteFliterKey]; 23 | siteEntity.isAutoDownload = site[DMHYSiteAutoDLKey]; 24 | siteEntity.downloadType = site[DMHYSiteDLTypeKey]; 25 | siteEntity.isDownloadFin = site[DMHYSiteDLFinKey]; 26 | siteEntity.responseType = site[DMHYSiteResponseTypeKey]; 27 | siteEntity.isCurrentUse = site[DMHYSiteCurrentUseKey]; 28 | siteEntity.createDate = [NSDate new]; 29 | return siteEntity; 30 | } 31 | 32 | @end 33 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYTorrent+CoreDataProperties.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYTorrent+CoreDataProperties.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 16/2/28. 6 | // Copyright © 2016年 yaqinking. All rights reserved. 7 | // 8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu 9 | // to delete and recreate this implementation file for your updated model. 10 | // 11 | 12 | #import "DMHYTorrent.h" 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface DMHYTorrent (CoreDataProperties) 17 | 18 | @property (nullable, nonatomic, retain) NSString *author; 19 | @property (nullable, nonatomic, retain) NSString *category; 20 | @property (nullable, nonatomic, retain) NSNumber *isDownloaded; 21 | @property (nullable, nonatomic, retain) NSNumber *isNewTorrent; 22 | @property (nullable, nonatomic, retain) NSString *link; 23 | @property (nullable, nonatomic, retain) NSString *magnet; 24 | @property (nullable, nonatomic, retain) NSDate *pubDate; 25 | @property (nullable, nonatomic, retain) NSString *title; 26 | @property (nullable, nonatomic, retain) DMHYKeyword *keyword; 27 | 28 | @end 29 | 30 | NS_ASSUME_NONNULL_END 31 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYTorrent+CoreDataProperties.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYTorrent+CoreDataProperties.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 16/2/28. 6 | // Copyright © 2016年 yaqinking. All rights reserved. 7 | // 8 | // Choose "Create NSManagedObject Subclass…" from the Core Data editor menu 9 | // to delete and recreate this implementation file for your updated model. 10 | // 11 | 12 | #import "DMHYTorrent+CoreDataProperties.h" 13 | 14 | @implementation DMHYTorrent (CoreDataProperties) 15 | 16 | @dynamic author; 17 | @dynamic category; 18 | @dynamic isDownloaded; 19 | @dynamic isNewTorrent; 20 | @dynamic link; 21 | @dynamic magnet; 22 | @dynamic pubDate; 23 | @dynamic title; 24 | @dynamic keyword; 25 | 26 | @end 27 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYTorrent.h: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYTorrent.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/23/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import 11 | 12 | @class DMHYKeyword; 13 | 14 | NS_ASSUME_NONNULL_BEGIN 15 | 16 | @interface DMHYTorrent : NSManagedObject 17 | 18 | // Insert code here to declare functionality of your managed object subclass 19 | 20 | @end 21 | 22 | NS_ASSUME_NONNULL_END 23 | 24 | #import "DMHYTorrent+CoreDataProperties.h" 25 | -------------------------------------------------------------------------------- /DMHY/Model/DMHYTorrent.m: -------------------------------------------------------------------------------- 1 | // 2 | // DMHYTorrent.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/23/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "DMHYTorrent.h" 10 | #import "DMHYKeyword.h" 11 | 12 | @implementation DMHYTorrent 13 | 14 | // Insert code here to add functionality to your managed object subclass 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /DMHY/Model/FileItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileItem.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/26/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FileItem : NSObject 12 | 13 | @property (nonatomic, strong) NSURL *url; 14 | @property (nonatomic, strong) NSString *fileName; 15 | @property (nonatomic, strong) NSDate *modifyDate; 16 | 17 | - (instancetype )initWithURL:(NSURL *)url fileName:(NSString *)name modifyDate:(NSDate *)date; 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DMHY/Model/FileItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileItem.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/26/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "FileItem.h" 10 | 11 | @implementation FileItem 12 | 13 | - (instancetype)initWithURL:(NSURL *)url fileName:(NSString *)name modifyDate:(NSDate *)date { 14 | self = [super init]; 15 | if (self) { 16 | _url = url; 17 | _fileName = name; 18 | _modifyDate = date; 19 | } 20 | return self; 21 | } 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /DMHY/NavigationView.h: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationView.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/9/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface NavigationView : NSView 12 | @end 13 | -------------------------------------------------------------------------------- /DMHY/NavigationView.m: -------------------------------------------------------------------------------- 1 | // 2 | // NavigationView.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/9/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "NavigationView.h" 10 | #import "PreferenceController.h" 11 | 12 | #define DMHYThemeKey @"ThemeType" 13 | 14 | typedef NS_ENUM(NSInteger, DMHYThemeType) { 15 | DMHYThemeLight, 16 | DMHYThemeDark 17 | }; 18 | 19 | @implementation NavigationView 20 | 21 | - (void)drawRect:(NSRect)dirtyRect { 22 | [super drawRect:dirtyRect]; 23 | 24 | // Drawing code here. 25 | 26 | NSInteger themecode = [PreferenceController preferenceTheme]; 27 | switch (themecode) { 28 | case DMHYThemeLight: 29 | self.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantLight]; 30 | break; 31 | case DMHYThemeDark: 32 | self.appearance = [NSAppearance appearanceNamed:NSAppearanceNameVibrantDark]; 33 | default: 34 | break; 35 | } 36 | } 37 | 38 | - (BOOL)allowsVibrancy { 39 | return YES; 40 | } 41 | 42 | - (void)viewWillDraw { 43 | 44 | } 45 | 46 | @end 47 | -------------------------------------------------------------------------------- /DMHY/PreferenceController.h: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/16/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MASPreferencesViewController.h" 11 | 12 | extern NSString * const FliterKeywordKey; 13 | extern NSString * const DontDownloadCollectionKey; 14 | extern NSString * const DMHYDontDownloadCollectionKeyDidChangedNotification; 15 | 16 | @interface PreferenceController : NSViewController 17 | 18 | + (void)setPreferenceDownloadLinkType:(BOOL)type; 19 | + (BOOL)preferenceDownloadLinkType; 20 | 21 | + (void)setPreferenceSavePath:(NSURL *)path; 22 | + (NSURL *)preferenceSavePath; 23 | 24 | + (void)setFileWatchPath:(NSURL *)path; 25 | + (NSURL *)fileWatchPath; 26 | 27 | + (void)setPreferenceFetchInterval:(NSInteger) seconds; 28 | + (NSInteger)preferenceFetchInterval; 29 | 30 | + (void)setPreferenceTheme:(NSInteger) themeCode; 31 | + (NSInteger)preferenceTheme; 32 | 33 | + (void)setPreferenceDontDownloadCollection:(BOOL)value; 34 | + (BOOL)preferenceDontDownloadCollection; 35 | 36 | + (NSURL *)userDownloadPath; 37 | 38 | + (void)setViewPreferenceTableViewRowStyle:(NSInteger)style; 39 | + (NSInteger)viewPreferenceTableViewRowStyle; 40 | + (void)setPreferenceDoubleAction:(NSInteger)action; 41 | + (NSInteger)preferenceDoubleAction; 42 | 43 | @end 44 | -------------------------------------------------------------------------------- /DMHY/PreferenceController.m: -------------------------------------------------------------------------------- 1 | // 2 | // PreferenceController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/16/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "PreferenceController.h" 10 | #import "SitePreferenceController.h" 11 | #import "DMHYAPI.h" 12 | #import "DMHYCoreDataStackManager.h" 13 | #include 14 | 15 | NSString * const FliterKeywordKey = @"FliterKeyword"; 16 | NSString * const DontDownloadCollectionKey = @"DontDownloadCollection"; 17 | NSString * const DMHYDontDownloadCollectionKeyDidChangedNotification = @"DMHYDontDownloadCollectionKeyDidChangedNotification"; 18 | 19 | @interface PreferenceController () 20 | @property (weak) IBOutlet NSMatrix *downloadLinkTypeMatrix; 21 | @property (weak) IBOutlet NSTextField *savePathLabel; 22 | @property (weak) IBOutlet NSTextField *fileWatchPathLabel; 23 | 24 | @property (weak) IBOutlet NSPopUpButton *fetchIntervalPopUpButton; 25 | 26 | @property (weak) IBOutlet NSTextField *fliterKeywordTextField; 27 | 28 | @property (weak) IBOutlet NSButton *dontDownloadCollectionButton; 29 | 30 | @property (weak) IBOutlet NSMatrix *mainViewRowStyleMatrix; 31 | @property (weak) IBOutlet NSMatrix *doubleActionMatrix; 32 | 33 | @end 34 | 35 | @implementation PreferenceController 36 | 37 | - (instancetype)init { 38 | return [super initWithNibName:@"Preference" bundle:nil]; 39 | } 40 | 41 | - (NSString *)identifier 42 | { 43 | return @"GeneralPreferences"; 44 | } 45 | 46 | - (NSImage *)toolbarItemImage 47 | { 48 | return [NSImage imageNamed:NSImageNamePreferencesGeneral]; 49 | } 50 | 51 | - (NSString *)toolbarItemLabel 52 | { 53 | return @"常用"; 54 | } 55 | 56 | 57 | 58 | - (void)viewDidLoad { 59 | [super viewDidLoad]; 60 | [self configurePreference]; 61 | } 62 | 63 | - (void)configurePreference { 64 | NSInteger idx = [PreferenceController preferenceDownloadLinkType]; 65 | [self.downloadLinkTypeMatrix selectCellAtRow:idx column:0]; 66 | 67 | NSURL *url = [PreferenceController preferenceSavePath]; 68 | if (url) { 69 | self.savePathLabel.stringValue = [url path]; 70 | } else { 71 | self.savePathLabel.stringValue = [[PreferenceController userDownloadPath] path]; 72 | } 73 | 74 | NSURL *fileWatchPath = [PreferenceController fileWatchPath]; 75 | if (fileWatchPath) { 76 | self.fileWatchPathLabel.stringValue = fileWatchPath.path; 77 | } else { 78 | self.fileWatchPathLabel.stringValue = @"请设置文件查看路径 <_<"; 79 | } 80 | 81 | NSInteger seconds = [PreferenceController preferenceFetchInterval]; 82 | NSInteger minutes = seconds / 60; 83 | 84 | [self.fetchIntervalPopUpButton selectItemWithTitle:[NSString stringWithFormat:@"%li",(long)minutes]]; 85 | 86 | NSInteger dontDownloadCollection = [PreferenceController preferenceDontDownloadCollection]; 87 | self.dontDownloadCollectionButton.state = dontDownloadCollection; 88 | 89 | NSString *fliter = [[NSUserDefaults standardUserDefaults] stringForKey:FliterKeywordKey]; 90 | self.fliterKeywordTextField.stringValue = fliter; 91 | 92 | NSInteger rowStyleIdx = [PreferenceController viewPreferenceTableViewRowStyle]; 93 | [self.mainViewRowStyleMatrix selectCellAtRow:rowStyleIdx column:0]; 94 | NSInteger rowDoubleAction = [PreferenceController preferenceDoubleAction]; 95 | [self.doubleActionMatrix selectCellAtRow:rowDoubleAction column:0]; 96 | } 97 | 98 | - (void)controlTextDidEndEditing:(NSNotification *)obj { 99 | NSString *fliter = ((NSTextField *)obj.object).stringValue; 100 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 101 | [userDefaults setObject:fliter forKey:FliterKeywordKey]; 102 | [userDefaults synchronize]; 103 | } 104 | 105 | - (IBAction)downloadLinkTypeChanged:(id)sender { 106 | //这里仅有 0 1 这两中,直接偷懒(毕竟 YES = 1 NO = 0) 107 | NSInteger linkType = [self.downloadLinkTypeMatrix selectedRow]; 108 | [PreferenceController setPreferenceDownloadLinkType:linkType]; 109 | [DMHYNotification postNotificationName:DMHYDownloadLinkTypeNotification]; 110 | } 111 | 112 | - (IBAction)changeSavePath:(id)sender { 113 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 114 | openPanel.canChooseFiles = NO; 115 | openPanel.canChooseDirectories = YES; 116 | openPanel.prompt = @"OK"; 117 | if ([openPanel runModal] == NSModalResponseOK) { 118 | [PreferenceController setPreferenceSavePath:[openPanel URL]]; 119 | self.savePathLabel.stringValue = [[openPanel URL] path]; 120 | } 121 | [DMHYNotification postNotificationName:DMHYSavePathChangedNotification]; 122 | } 123 | 124 | - (IBAction)changeFileWatchPath:(id)sender { 125 | NSOpenPanel *openPanel = [NSOpenPanel openPanel]; 126 | openPanel.canChooseFiles = NO; 127 | openPanel.canChooseDirectories = YES; 128 | openPanel.prompt = @"OK"; 129 | if ([openPanel runModal] == NSModalResponseOK) { 130 | NSURL *url = [openPanel URL]; 131 | [PreferenceController setFileWatchPath:url]; 132 | self.fileWatchPathLabel.stringValue = [[openPanel URL] path]; 133 | } 134 | [DMHYNotification postNotificationName:DMHYFileWatchPathChangedNotification]; 135 | } 136 | 137 | - (IBAction)changeFetchInterval:(id)sender { 138 | NSInteger minitues = [((NSPopUpButton *)sender).titleOfSelectedItem integerValue]; 139 | NSInteger seconds = minitues * 60; 140 | [PreferenceController setPreferenceFetchInterval:seconds]; 141 | [DMHYNotification postNotificationName:DMHYFetchIntervalChangedNotification]; 142 | } 143 | 144 | - (IBAction)dontDownloadCollection:(id)sender { 145 | NSInteger state = ((NSButton *)sender).state; 146 | [PreferenceController setPreferenceDontDownloadCollection:state]; 147 | [DMHYNotification postNotificationName:DMHYDontDownloadCollectionKeyDidChangedNotification]; 148 | } 149 | 150 | + (NSURL *)userDownloadPath { 151 | NSURL *downloadDirectoryURL = [[NSFileManager defaultManager] URLForDirectory:NSDownloadsDirectory 152 | inDomain:NSUserDomainMask 153 | appropriateForURL:nil 154 | create:NO 155 | error:nil]; 156 | return downloadDirectoryURL; 157 | } 158 | 159 | + (void)setPreferenceDownloadLinkType:(BOOL)type { 160 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 161 | [userDefaults setBool:type forKey:kDownloadLinkType]; 162 | [userDefaults synchronize]; 163 | } 164 | 165 | + (BOOL)preferenceDownloadLinkType { 166 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 167 | return [userDefaults boolForKey:kDownloadLinkType]; 168 | } 169 | 170 | + (void)setPreferenceDontDownloadCollection:(BOOL)value { 171 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 172 | [userDefaults setBool:value forKey:DontDownloadCollectionKey]; 173 | [userDefaults synchronize]; 174 | } 175 | 176 | + (BOOL)preferenceDontDownloadCollection { 177 | return [[NSUserDefaults standardUserDefaults] boolForKey:DontDownloadCollectionKey]; 178 | } 179 | 180 | + (void)setPreferenceSavePath:(NSURL *)path { 181 | NSUserDefaults *userDefautls = [NSUserDefaults standardUserDefaults]; 182 | [userDefautls setURL:path forKey:kSavePath]; 183 | [userDefautls synchronize]; 184 | } 185 | 186 | + (NSURL *)preferenceSavePath { 187 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 188 | return [userDefaults URLForKey:kSavePath]; 189 | } 190 | 191 | + (void)setFileWatchPath:(NSURL *)url { 192 | NSData *data = [url bookmarkDataWithOptions:NSURLBookmarkCreationWithSecurityScope includingResourceValuesForKeys:nil relativeToURL:nil error:nil]; 193 | NSUserDefaults *userDefautls = [NSUserDefaults standardUserDefaults]; 194 | [userDefautls setObject:data forKey:kFileWatchPath]; 195 | [userDefautls synchronize]; 196 | [DMHYNotification postNotificationName:DMHYFileWatchPathChangedNotification]; 197 | } 198 | 199 | + (NSURL *)fileWatchPath { 200 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 201 | NSData *filePathData = [userDefaults objectForKey:kFileWatchPath]; 202 | BOOL bookmarkISStable = NO; 203 | NSURL *fileWatchPath = [NSURL URLByResolvingBookmarkData:filePathData options:NSURLBookmarkResolutionWithSecurityScope relativeToURL:nil bookmarkDataIsStale:&bookmarkISStable error:nil]; 204 | return fileWatchPath; 205 | } 206 | 207 | + (void)setPreferenceFetchInterval:(NSInteger)seconds { 208 | NSUserDefaults *userDefautls = [NSUserDefaults standardUserDefaults]; 209 | [userDefautls setInteger:seconds forKey:kFetchInterval]; 210 | [userDefautls synchronize]; 211 | } 212 | 213 | + (NSInteger)preferenceFetchInterval { 214 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 215 | return [userDefaults integerForKey:kFetchInterval]; 216 | } 217 | 218 | + (void)setPreferenceTheme:(NSInteger)themeCode { 219 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 220 | switch (themeCode) { 221 | case DMHYThemeLight: 222 | [userDefaults setInteger:0 forKey:DMHYThemeKey]; 223 | break; 224 | case DMHYThemeDark: 225 | [userDefaults setInteger:1 forKey:DMHYThemeKey]; 226 | break; 227 | default: 228 | [userDefaults setInteger:0 forKey:DMHYThemeKey]; 229 | break; 230 | } 231 | [userDefaults synchronize]; 232 | } 233 | 234 | + (NSInteger)preferenceTheme { 235 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 236 | return [userDefaults integerForKey:DMHYThemeKey]; 237 | } 238 | 239 | + (void)setViewPreferenceTableViewRowStyle:(NSInteger)style { 240 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 241 | [userDefaults setInteger:style forKey:kMainTableViewRowStyle]; 242 | [userDefaults synchronize]; 243 | } 244 | 245 | + (NSInteger)viewPreferenceTableViewRowStyle { 246 | return [[NSUserDefaults standardUserDefaults] integerForKey:kMainTableViewRowStyle]; 247 | } 248 | 249 | + (void)setPreferenceDoubleAction:(NSInteger)action { 250 | NSUserDefaults *userDefaults = [NSUserDefaults standardUserDefaults]; 251 | [userDefaults setInteger:action forKey:kDoubleAction]; 252 | [userDefaults synchronize]; 253 | } 254 | 255 | + (NSInteger)preferenceDoubleAction { 256 | return [[NSUserDefaults standardUserDefaults] integerForKey:kDoubleAction]; 257 | } 258 | 259 | - (IBAction)mainTableViewRowStyleChanged:(id)sender { 260 | NSInteger style = [self.mainViewRowStyleMatrix selectedRow]; 261 | [PreferenceController setViewPreferenceTableViewRowStyle:style]; 262 | [DMHYNotification postNotificationName:DMHYMainTableViewRowStyleChangedNotification]; 263 | } 264 | 265 | - (IBAction)doubleActionChanged:(id)sender { 266 | NSInteger action = [self.doubleActionMatrix selectedRow]; 267 | [PreferenceController setPreferenceDoubleAction:action]; 268 | [DMHYNotification postNotificationName:DMHYDoubleActionChangedNotification]; 269 | } 270 | 271 | - (IBAction)resetPreference:(id)sender { 272 | NSAlert *alert = [[NSAlert alloc] init]; 273 | [alert setMessageText:@"确定要重置设置?"]; 274 | [alert setInformativeText:@"该操作会删除所有自定义设置。重置设置成功之后,会重新启动本应用 _(:3 」∠)_"]; 275 | [alert addButtonWithTitle:@"重置"]; 276 | [alert addButtonWithTitle:@"取消"]; 277 | [alert setAlertStyle:NSWarningAlertStyle]; 278 | 279 | [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) { 280 | if (returnCode == NSAlertFirstButtonReturn) { 281 | NSString *domainName = [[NSBundle mainBundle] bundleIdentifier]; 282 | [[NSUserDefaults standardUserDefaults] removePersistentDomainForName:domainName]; 283 | [[NSUserDefaults standardUserDefaults] synchronize]; 284 | [self relaunchAfterDelay:1.0]; 285 | } 286 | }]; 287 | 288 | } 289 | 290 | - (void)relaunchAfterDelay:(float)seconds { 291 | NSTask *task = [[NSTask alloc] init]; 292 | NSMutableArray *args = [NSMutableArray array]; 293 | [args addObject:@"-c"]; 294 | [args addObject:[NSString stringWithFormat:@"sleep %f; open \"%@\"", seconds, [[NSBundle mainBundle] bundlePath]]]; 295 | [task setLaunchPath:@"/bin/sh"]; 296 | [task setArguments:args]; 297 | [task launch]; 298 | [NSApp terminate:self]; 299 | } 300 | 301 | - (IBAction)resetDatabase:(id)sender { 302 | NSAlert *alert = [[NSAlert alloc] init]; 303 | [alert setMessageText:@"确定要重置数据库?"]; 304 | [alert setInformativeText:@"该操作会删除所有已保存的关键字和已下载的种子记录。重置数据库成功之后,会自动重新启动本应用 _(:3 」∠)_"]; 305 | [alert addButtonWithTitle:@"重置"]; 306 | [alert addButtonWithTitle:@"取消"]; 307 | [alert setAlertStyle:NSWarningAlertStyle]; 308 | 309 | [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) { 310 | if (returnCode == NSAlertFirstButtonReturn) { 311 | if ([[DMHYCoreDataStackManager sharedManager] resetDatabase]) { 312 | [self relaunchAfterDelay:1.0]; 313 | } 314 | } 315 | }]; 316 | 317 | } 318 | 319 | @end 320 | -------------------------------------------------------------------------------- /DMHY/Preferences/SitePreferenceController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SitePreferenceController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/22/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | #import "MASPreferencesViewController.h" 11 | 12 | extern NSString * const SiteResponseJSON; 13 | extern NSString * const SiteResponseXML; 14 | 15 | @interface SitePreferenceController : NSViewController 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /DMHY/Preferences/SitePreferenceController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SitePreferenceController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/22/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "SitePreferenceController.h" 10 | #import "DMHYCoreDataStackManager.h" 11 | #import "ButtonTableCellView.h" 12 | 13 | NSString * const SiteResponseJSON = @"JSON"; 14 | NSString * const SiteResponseXML = @"XML"; 15 | 16 | @interface SitePreferenceController () 17 | 18 | @property (weak) IBOutlet NSTableView *tableView; 19 | 20 | @property (nonatomic, strong) NSArray *sites; 21 | 22 | @end 23 | 24 | @implementation SitePreferenceController 25 | 26 | - (void)viewDidLoad { 27 | [super viewDidLoad]; 28 | // Do view setup here. 29 | [self observeNotifications]; 30 | [self.tableView reloadData]; 31 | } 32 | 33 | - (void)observeNotifications { 34 | [DMHYNotification addObserver:self selector:@selector(handleSeedDataCompleted) name:DMHYSeedDataCompletedNotification]; 35 | } 36 | 37 | - (void)handleSeedDataCompleted { 38 | self.sites = nil; 39 | [self.tableView reloadData]; 40 | } 41 | 42 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { 43 | return self.sites.count; 44 | } 45 | 46 | - (CGFloat)tableView:(NSTableView *)tableView heightOfRow:(NSInteger)row { 47 | return 22.0f; 48 | } 49 | 50 | - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { 51 | NSString *identifier = tableColumn.identifier; 52 | DMHYSite *site = self.sites[row]; 53 | 54 | if ([identifier isEqualToString:@"currentUseCell"]) { 55 | ButtonTableCellView *cellView = [tableView makeViewWithIdentifier:@"currentUseCell" owner:self]; 56 | [cellView.checkButton setState:site.isCurrentUse.integerValue]; 57 | return cellView; 58 | } 59 | if ([identifier isEqualToString:@"autoDownloadCell"]) { 60 | ButtonTableCellView *cellView = [tableView makeViewWithIdentifier:@"autoDownloadCell" owner:self]; 61 | [cellView.checkButton setState:site.isAutoDownload.integerValue]; 62 | return cellView; 63 | } 64 | if ([identifier isEqualToString:@"siteNameCell"]) { 65 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"siteNameCell" owner:self]; 66 | cellView.textField.stringValue = site.name; 67 | return cellView; 68 | } 69 | 70 | if ([identifier isEqualToString:@"responseTypeCell"]) { 71 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"responseTypeCell" owner:self]; 72 | cellView.textField.stringValue = site.responseType; 73 | return cellView; 74 | } 75 | if ([identifier isEqualToString:@"siteMainCell"]) { 76 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"siteMainCell" owner:self]; 77 | cellView.textField.stringValue = site.mainURL; 78 | return cellView; 79 | } 80 | return nil; 81 | } 82 | 83 | - (IBAction)changeAutoDownload:(NSButton *)sender { 84 | NSInteger state = sender.state; 85 | NSInteger row = [self.tableView rowForView:sender]; 86 | if (state < 0 || state > 1 || row < 0) { 87 | return; 88 | } 89 | DMHYSite *site = self.sites[row]; 90 | site.isAutoDownload = @(state); 91 | [[DMHYCoreDataStackManager sharedManager] saveContext]; 92 | [DMHYNotification postNotificationName:DMHYAutoDownloadSiteChangedNotification]; 93 | } 94 | 95 | - (IBAction)changeCurrentUse:(NSButton *)sender { 96 | NSInteger state = sender.state; 97 | NSInteger row = [self.tableView rowForView:sender]; 98 | if (state < 0 || state > 1 || row < 0) { 99 | return; 100 | } 101 | DMHYSite *currentUseSite = [[DMHYCoreDataStackManager sharedManager] currentUseSite]; 102 | currentUseSite.isCurrentUse = @NO; 103 | DMHYSite *site = self.sites[row]; 104 | site.isCurrentUse = @(state); 105 | [[DMHYCoreDataStackManager sharedManager] saveContext]; 106 | [self.tableView reloadData]; 107 | [DMHYNotification postNotificationName:DMHYSearchSiteChangedNotification object:site]; 108 | } 109 | 110 | -(NSArray *)sites { 111 | if (!_sites) { 112 | _sites = [[DMHYCoreDataStackManager sharedManager] allSites]; 113 | } 114 | return _sites; 115 | } 116 | 117 | - (NSString *)identifier { 118 | return @"SitesPreferences"; 119 | } 120 | 121 | - (NSImage *)toolbarItemImage { 122 | return [NSImage imageNamed:NSImageNameNetwork]; 123 | } 124 | 125 | - (NSString *)toolbarItemLabel { 126 | return @"站点"; 127 | } 128 | 129 | @end 130 | -------------------------------------------------------------------------------- /DMHY/SavedDataViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SavedDataViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/25/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface SavedDataViewController : NSViewController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/SavedDataViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SavedDataViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/25/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "SavedDataViewController.h" 10 | #import "DMHYAPI.h" 11 | #import "DMHYCoreDataStackManager.h" 12 | #import "DMHYTorrent.h" 13 | #import "DMHYTool.h" 14 | #import "PreferenceController.h" 15 | 16 | @interface SavedDataViewController () 17 | 18 | @property (weak) IBOutlet NSTableView *tableView; 19 | 20 | @property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext; 21 | @property (nonatomic, strong) NSMutableArray *torrents; 22 | 23 | @end 24 | 25 | @implementation SavedDataViewController 26 | 27 | @synthesize managedObjectContext = _context; 28 | 29 | - (void)viewDidLoad { 30 | [super viewDidLoad]; 31 | [self observeNotification]; 32 | [self setupTableViewStyle]; 33 | [self setupTableViewSort]; 34 | [self setupMenuItems]; 35 | } 36 | 37 | #pragma mark - Setup 38 | 39 | - (void)setupTableViewStyle { 40 | self.tableView.rowSizeStyle = [self preferedRowSizeStyle]; 41 | self.tableView.usesAlternatingRowBackgroundColors = YES; 42 | self.tableView.columnAutoresizingStyle = NSTableViewUniformColumnAutoresizingStyle; 43 | [self.tableView sizeLastColumnToFit]; 44 | } 45 | 46 | - (void)setupTableViewSort { 47 | [self.tableView setSortDescriptors:[NSArray arrayWithObjects: 48 | [NSSortDescriptor sortDescriptorWithKey:@"pubDate" ascending:YES selector:@selector(compare:)], 49 | [NSSortDescriptor sortDescriptorWithKey:@"title" ascending:YES selector:@selector(compare:)], 50 | nil]]; 51 | } 52 | 53 | - (void)setupMenuItems { 54 | NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu]; 55 | NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"]; 56 | NSMenu *editSubMenu = [editMenuItem submenu]; 57 | unichar deleteKey = NSBackspaceCharacter; 58 | NSString *delete = [NSString stringWithCharacters:&deleteKey length:1]; 59 | NSMenuItem *removeTorrentMenuItem = [[NSMenuItem alloc] initWithTitle:@"删除种子数据" 60 | action:@selector(deleteSelectTorrent) 61 | keyEquivalent:delete]; 62 | removeTorrentMenuItem.keyEquivalentModifierMask = NSCommandKeyMask | NSShiftKeyMask; 63 | 64 | [editSubMenu addItem:removeTorrentMenuItem]; 65 | } 66 | 67 | - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { 68 | NSString *title = [menuItem title]; 69 | if ([title isEqualToString:@"删除种子数据"]) { 70 | //什么都没选的时候 71 | if (self.tableView.selectedRow == -1) { 72 | return NO; 73 | } 74 | } 75 | return YES; 76 | } 77 | 78 | - (NSTableViewRowSizeStyle )preferedRowSizeStyle { 79 | NSInteger rowStyle = [PreferenceController viewPreferenceTableViewRowStyle]; 80 | switch (rowStyle) { 81 | case 0: 82 | return NSTableViewRowSizeStyleSmall; 83 | case 1: 84 | return NSTableViewRowSizeStyleMedium; 85 | case 2: 86 | return NSTableViewRowSizeStyleLarge; 87 | default: 88 | break; 89 | } 90 | return NSTableViewRowSizeStyleSmall; 91 | } 92 | 93 | #pragma mark - NSTableViewDelegate 94 | 95 | - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { 96 | NSString *identifier = tableColumn.identifier; 97 | DMHYTorrent *torrent = self.torrents[row]; 98 | if ([identifier isEqualToString:@"pubDateCell"]) { 99 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"pubDateCell" owner:self]; 100 | NSString *pubDate = [[DMHYTool tool] stringFromSavedDate:torrent.pubDate]; 101 | cellView.textField.stringValue = pubDate ? pubDate : @""; 102 | return cellView; 103 | } 104 | if ([identifier isEqualToString:@"titleCell"]) { 105 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"titleCell" owner:self]; 106 | cellView.textField.stringValue = torrent.title ? torrent.title : @""; 107 | return cellView; 108 | } 109 | 110 | if ([identifier isEqualToString:@"linkCell"]) { 111 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"linkCell" owner:self]; 112 | cellView.textField.stringValue = torrent.link ? torrent.link : @""; 113 | return cellView; 114 | } 115 | if ([identifier isEqualToString:@"magnetCell"]) { 116 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"magnetCell" owner:self]; 117 | cellView.textField.stringValue = torrent.magnet ? torrent.magnet : @""; 118 | return cellView; 119 | } 120 | return nil; 121 | } 122 | 123 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { 124 | return self.torrents.count; 125 | } 126 | 127 | - (void)tableView:(NSTableView *)tableView sortDescriptorsDidChange:(NSArray *)oldDescriptors { 128 | } 129 | 130 | - (void)deleteSelectTorrent { 131 | if (self.tableView.selectedRow < 0) { 132 | return; 133 | } 134 | NSIndexSet *selectSet = [self.tableView selectedRowIndexes]; 135 | [selectSet enumerateIndexesUsingBlock:^(NSUInteger idx, BOOL * _Nonnull stop) { 136 | DMHYTorrent *torrent = self.torrents[idx]; 137 | [self.managedObjectContext deleteObject:torrent]; 138 | }]; 139 | NSError *error = nil; 140 | if ([self.managedObjectContext hasChanges]) { 141 | if (![self.managedObjectContext save:&error]) { 142 | [NSApp presentError:error]; 143 | }; 144 | } 145 | [self.torrents removeObjectsAtIndexes:selectSet]; 146 | [self.tableView reloadData]; 147 | } 148 | 149 | - (void)observeNotification { 150 | [DMHYNotification addObserver:self selector:@selector(handleThemeChanged) name:DMHYThemeChangedNotification]; 151 | [DMHYNotification addObserver:self selector:@selector(handleMainTableViewRowStyleChanged) name:DMHYMainTableViewRowStyleChangedNotification]; 152 | [DMHYNotification addObserver:self selector:@selector(handleDatabaseChanged) name:DMHYDatabaseChangedNotification]; 153 | } 154 | 155 | - (void)handleDatabaseChanged { 156 | self.torrents = nil; 157 | [self.tableView reloadData]; 158 | } 159 | 160 | - (void)handleThemeChanged { 161 | [self.view setNeedsDisplay:YES]; 162 | } 163 | 164 | - (void)handleMainTableViewRowStyleChanged { 165 | [self setupTableViewStyle]; 166 | [self.tableView setNeedsDisplay:YES]; 167 | } 168 | 169 | - (NSMutableArray *)torrents { 170 | if (!_torrents) { 171 | NSFetchRequest *request = [NSFetchRequest fetchRequestWithEntityName:DMHYTorrentEntityKey]; 172 | request.sortDescriptors = @[[NSSortDescriptor sortDescriptorWithKey:@"pubDate" ascending:NO]]; 173 | _torrents = [[self.managedObjectContext executeFetchRequest:request 174 | error:NULL] mutableCopy]; 175 | } 176 | return _torrents; 177 | } 178 | 179 | - (NSManagedObjectContext *)managedObjectContext { 180 | if (!_context) { 181 | _context = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType]; 182 | _context.persistentStoreCoordinator = [[DMHYCoreDataStackManager sharedManager] persistentStoreCoordinator]; 183 | _context.undoManager = nil; 184 | } 185 | return _context; 186 | } 187 | @end 188 | -------------------------------------------------------------------------------- /DMHY/SideViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // SideViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/19/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | /** 12 | * 已保存的关键字 13 | */ 14 | @interface SideViewController : NSViewController 15 | 16 | @end 17 | -------------------------------------------------------------------------------- /DMHY/SideViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // SideViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/19/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "SideViewController.h" 10 | #import "DMHYCoreDataStackManager.h" 11 | 12 | @interface SideViewController () 13 | 14 | @property (weak) IBOutlet NSPopUpButton *sitesPopUpButton; 15 | @property (weak) IBOutlet NSOutlineView *outlineView; 16 | 17 | @property (nonatomic, readonly) NSManagedObjectContext *managedObjectContext; 18 | @property (nonatomic, strong ) NSMutableArray *keywords; 19 | @property (nonatomic, strong) DMHYSite *site; 20 | 21 | @end 22 | 23 | @implementation SideViewController 24 | 25 | @synthesize managedObjectContext = _context; 26 | 27 | - (void)viewDidLoad { 28 | [super viewDidLoad]; 29 | [self observeNotification]; 30 | [self setupPopupButtonData]; 31 | [self expandOutlineViewItem]; 32 | [self setupMenuItems]; 33 | } 34 | 35 | - (void)expandOutlineViewItem { 36 | for (int i = 0 ; i < self.keywords.count; i++) { 37 | [self.outlineView expandItem:self.keywords[i]]; 38 | } 39 | } 40 | 41 | #pragma mark - IBAction 42 | 43 | 44 | - (IBAction)siteChanged:(NSPopUpButton *)sender { 45 | NSMenuItem *item = [self.sitesPopUpButton selectedItem]; 46 | NSInteger selectIndex = [self.sitesPopUpButton indexOfItem:item]; 47 | DMHYSite *site = [[[DMHYCoreDataStackManager sharedManager] allSites] objectAtIndex:selectIndex]; 48 | self.site = site; 49 | self.keywords = nil; 50 | [self reloadData]; 51 | [DMHYNotification postNotificationName:DMHYSearchSiteChangedNotification object:site]; 52 | } 53 | 54 | #pragma mark - Setup Data 55 | 56 | - (void)setupPopupButtonData { 57 | NSMutableArray *siteNames = [[NSMutableArray alloc] init]; 58 | NSArray *sites = [[DMHYCoreDataStackManager sharedManager] allSites]; 59 | [sites enumerateObjectsUsingBlock:^(DMHYSite * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) { 60 | [siteNames addObject:obj.name]; 61 | }]; 62 | [self.sitesPopUpButton addItemsWithTitles:siteNames]; 63 | [self.sitesPopUpButton selectItemWithTitle:self.site.name]; 64 | } 65 | 66 | - (void)setupMenuItems { 67 | NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu]; 68 | NSMenuItem *editMenuItem = [mainMenu itemWithTitle:@"Edit"]; 69 | NSMenu *editSubMenu = [editMenuItem submenu]; 70 | unichar deleteKey = NSBackspaceCharacter; 71 | NSString *delete = [NSString stringWithCharacters:&deleteKey length:1]; 72 | NSMenuItem *removeSubKeywordMenuItem = [[NSMenuItem alloc] initWithTitle:@"删除关键字" 73 | action:@selector(deleteSubKeyword) 74 | keyEquivalent:delete]; 75 | [editSubMenu addItem:removeSubKeywordMenuItem]; 76 | } 77 | 78 | #pragma mark - NSOutlineViewDataSource 79 | 80 | - (NSInteger)outlineView:(NSOutlineView *)outlineView numberOfChildrenOfItem:(DMHYKeyword *) item { 81 | return !item ? [self.keywords count] : [item.subKeywords count]; 82 | } 83 | 84 | - (BOOL)outlineView:(NSOutlineView *)outlineView isItemExpandable:(DMHYKeyword *)item { 85 | return !item ? YES : [item.subKeywords count] != 0; 86 | } 87 | 88 | - (id)outlineView:(NSOutlineView *)outlineView child:(NSInteger)index ofItem:(DMHYKeyword *)item { 89 | // NSLog(@"item -> keyword %@",item.keyword); 90 | NSArray *subKeywords = [item.subKeywords allObjects]; 91 | return !item ? self.keywords[index] : subKeywords[index]; 92 | } 93 | 94 | - (id)outlineView:(NSOutlineView *)outlineView objectValueForTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { 95 | DMHYKeyword *weekday = item; 96 | NSString *identifier = tableColumn.identifier; 97 | if ([identifier isEqualToString:@"Keyword"]) { 98 | return weekday.keyword; 99 | } 100 | return nil; 101 | } 102 | 103 | - (void)outlineView:(NSOutlineView *)outlineView setObjectValue:(id)object forTableColumn:(NSTableColumn *)tableColumn byItem:(id)item { 104 | DMHYKeyword *keyword = item; 105 | NSString *newKeyword = object; 106 | 107 | if (!keyword.isSubKeyword.boolValue || [newKeyword isEqualToString:@""]) { 108 | return; 109 | } 110 | 111 | NSString *identifier = tableColumn.identifier; 112 | if ([identifier isEqualToString:@"Keyword"]) { 113 | keyword.keyword = newKeyword; 114 | [self saveData]; 115 | } 116 | } 117 | 118 | #pragma mark - NSOutlineViewDelegate 119 | 120 | - (void)outlineViewSelectionDidChange:(NSNotification *)notification { 121 | DMHYKeyword *selectedKeyword = [self.outlineView itemAtRow:[self.outlineView selectedRow]]; 122 | NSString *keyword = selectedKeyword.keyword; 123 | if (!selectedKeyword.isSubKeyword.boolValue || keyword.length == 0) { 124 | return; 125 | } 126 | NSNumber *isSubKeyword = selectedKeyword.isSubKeyword; 127 | NSDictionary *userInfo = @{kSelectKeyword : keyword, 128 | kSelectKeywordIsSubKeyword : isSubKeyword}; 129 | [DMHYNotification postNotificationName:DMHYSelectKeywordChangedNotification object:selectedKeyword userInfo:userInfo]; 130 | } 131 | 132 | - (BOOL)outlineView:(NSOutlineView *)outlineView shouldExpandItem:(id)item { 133 | return YES; 134 | } 135 | 136 | - (BOOL)outlineView:(NSOutlineView *)outlineView shouldEditTableColumn:(NSTableColumn *)tableColumn item:(id)item { 137 | return ((DMHYKeyword *)item).isSubKeyword.boolValue ? YES : NO; 138 | } 139 | 140 | #pragma mark - NSSplitViewDelegate 141 | 142 | - (NSRect)splitView:(NSSplitView *)splitView effectiveRect:(NSRect)proposedEffectiveRect forDrawnRect:(NSRect)drawnRect ofDividerAtIndex:(NSInteger)dividerIndex { 143 | return NSRectFromCGRect(CGRectZero); 144 | } 145 | 146 | #pragma mark - Notification 147 | 148 | - (void)observeNotification { 149 | [DMHYNotification addObserver:self selector:@selector(handleSeedDataComplete) name:DMHYSeedDataCompletedNotification]; 150 | [DMHYNotification addObserver:self selector:@selector(handleThemeChanged) name:DMHYThemeChangedNotification]; 151 | [DMHYNotification addObserver:self selector:@selector(handleKeywordAdded:) name:DMHYKeywordAddedNotification]; 152 | [DMHYNotification addObserver:self selector:@selector(handleSiteAdded) name:DMHYSiteAddedNotification]; 153 | } 154 | 155 | - (void)handleSiteAdded { 156 | [self reset]; 157 | [self setupPopupButtonData]; 158 | } 159 | 160 | - (void)handleKeywordAdded:(NSNotification *)notification { 161 | DMHYSite *site = notification.object; 162 | if ([site.name isEqualToString:self.site.name]) { 163 | self.site = site; 164 | self.keywords = nil; 165 | [self reloadData]; 166 | } 167 | } 168 | 169 | - (void)reset { 170 | self.site = nil; 171 | self.keywords = nil; 172 | [self reloadData]; 173 | } 174 | 175 | - (void)handleSeedDataComplete { 176 | [self reset]; 177 | [self setupPopupButtonData]; 178 | } 179 | 180 | - (void)handleThemeChanged { 181 | [self.view setNeedsDisplay:YES]; 182 | } 183 | 184 | #pragma mark - MenuItem 185 | 186 | - (void)deleteSubKeyword { 187 | NSInteger selectKeywordIndex = [self.outlineView selectedRow]; 188 | DMHYKeyword *keyword = [self.outlineView itemAtRow:selectKeywordIndex]; 189 | if ([keyword.isSubKeyword boolValue]) { 190 | DMHYKeyword *parentKeyword = [self.outlineView parentForItem:keyword]; 191 | [parentKeyword removeSubKeywordsObject:keyword]; 192 | [self.managedObjectContext deleteObject:keyword]; 193 | [self saveData]; 194 | [self reloadData]; 195 | [DMHYNotification postNotificationName:DMHYDatabaseChangedNotification]; 196 | } 197 | } 198 | 199 | - (BOOL)validateMenuItem:(NSMenuItem *)menuItem { 200 | NSString *title = [menuItem title]; 201 | if ([title isEqualToString:@"删除关键字"]) { 202 | //什么都没选的时候 203 | if (self.outlineView.selectedRow == -1) { 204 | return NO; 205 | } 206 | DMHYKeyword *keyword = [self.outlineView itemAtRow:[self.outlineView selectedRow]]; 207 | //记得使用 boolValue 获取 BOOL 值 >_< 208 | if (![keyword.isSubKeyword boolValue]) { 209 | return NO; 210 | } 211 | } 212 | return YES; 213 | } 214 | 215 | #pragma mark - Properties 216 | 217 | - (NSMutableArray *)keywords { 218 | 219 | if (!_keywords) { 220 | NSSortDescriptor *sortDesc = [[NSSortDescriptor alloc] initWithKey:@"createDate" ascending:YES]; 221 | NSPredicate *predicate = [NSPredicate predicateWithFormat:@"isSubKeyword != YES"]; 222 | NSArray *weekdayKeywords = [[self.site.keywords allObjects] filteredArrayUsingPredicate:predicate]; 223 | _keywords = [[weekdayKeywords sortedArrayUsingDescriptors:@[sortDesc]] mutableCopy]; 224 | } 225 | return _keywords; 226 | } 227 | 228 | - (DMHYSite *)site { 229 | if (!_site) { 230 | _site = [[DMHYCoreDataStackManager sharedManager] currentUseSite]; 231 | } 232 | return _site; 233 | } 234 | 235 | - (NSManagedObjectContext *)managedObjectContext { 236 | if (!_context) { 237 | _context = [[DMHYCoreDataStackManager sharedManager] managedObjectContext]; 238 | } 239 | return _context; 240 | } 241 | 242 | #pragma mark - Utils 243 | 244 | - (void)saveData { 245 | NSError *error = nil; 246 | if ([self.managedObjectContext hasChanges]) { 247 | if (![self.managedObjectContext save:&error]) { 248 | NSAlert *alert = [[NSAlert alloc] init]; 249 | [alert setMessageText:@"删除关键字时出现错误"]; 250 | [alert setInformativeText:[NSString stringWithFormat:@"%@", [error localizedDescription]]]; 251 | [alert addButtonWithTitle:@"OK"]; 252 | [alert setAlertStyle:NSWarningAlertStyle]; 253 | 254 | [alert beginSheetModalForWindow:self.view.window completionHandler:^(NSModalResponse returnCode) { 255 | if (returnCode == NSAlertFirstButtonReturn) { 256 | 257 | } 258 | }]; 259 | } 260 | } 261 | } 262 | 263 | - (void)reloadData { 264 | [self.outlineView reloadData]; 265 | [self expandOutlineViewItem]; 266 | } 267 | 268 | @end 269 | -------------------------------------------------------------------------------- /DMHY/TitleTableCellView.h: -------------------------------------------------------------------------------- 1 | // 2 | // TitleTableCellView.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/8/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TitleTableCellView : NSTableCellView 12 | @property (weak) IBOutlet NSButton *downloadButton; 13 | 14 | @end 15 | -------------------------------------------------------------------------------- /DMHY/TitleTableCellView.m: -------------------------------------------------------------------------------- 1 | // 2 | // TitleTableCellView.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/8/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "TitleTableCellView.h" 10 | 11 | @implementation TitleTableCellView 12 | 13 | - (void)drawRect:(NSRect)dirtyRect { 14 | [super drawRect:dirtyRect]; 15 | 16 | // Drawing code here. 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DMHY/TorrentItem.h: -------------------------------------------------------------------------------- 1 | // 2 | // TorrentItem.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 15/8/31. 6 | // Copyright © 2015年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface TorrentItem : NSObject 12 | 13 | @property (nonatomic, strong) NSString *title; 14 | @property (nonatomic, strong) NSURL *link; 15 | @property (nonatomic, strong) NSURL *magnet; 16 | @property (nonatomic, strong) NSString *pubDate; 17 | @property (nonatomic, strong) NSString *author; 18 | @property (nonatomic, strong) NSString *category; 19 | 20 | @end 21 | -------------------------------------------------------------------------------- /DMHY/TorrentItem.m: -------------------------------------------------------------------------------- 1 | // 2 | // TorrentItem.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 15/8/31. 6 | // Copyright © 2015年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "TorrentItem.h" 10 | 11 | @implementation TorrentItem 12 | 13 | 14 | - (NSString *)description { 15 | return [NSString stringWithFormat:@"PublishDate: %@ \n Title: %@ \n Author: %@ \n",self.pubDate,self.title,self.author]; 16 | } 17 | 18 | //- (id)valueForUndefinedKey:(NSString *)key { 19 | // 20 | // return nil; 21 | //} 22 | 23 | @end 24 | -------------------------------------------------------------------------------- /DMHY/View/ButtonTableCellView.h: -------------------------------------------------------------------------------- 1 | // 2 | // CheckTableCellView.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/17/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ButtonTableCellView : NSTableCellView 12 | 13 | @property (weak) IBOutlet NSButton *checkButton; 14 | 15 | @end 16 | -------------------------------------------------------------------------------- /DMHY/View/ButtonTableCellView.m: -------------------------------------------------------------------------------- 1 | // 2 | // CheckTableCellView.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 7/17/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "ButtonTableCellView.h" 10 | 11 | @implementation ButtonTableCellView 12 | 13 | - (void)drawRect:(NSRect)dirtyRect { 14 | [super drawRect:dirtyRect]; 15 | 16 | // Drawing code here. 17 | } 18 | 19 | @end 20 | -------------------------------------------------------------------------------- /DMHY/View/FileTableView.h: -------------------------------------------------------------------------------- 1 | // 2 | // FileTableView.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/27/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface FileTableView : NSTableView 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/View/FileTableView.m: -------------------------------------------------------------------------------- 1 | // 2 | // FileTableView.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/27/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "FileTableView.h" 10 | #import "AppDelegate.h" 11 | 12 | @implementation FileTableView 13 | 14 | - (void)keyDown:(NSEvent *)theEvent 15 | { 16 | NSString *key = [theEvent charactersIgnoringModifiers]; 17 | if ([key isEqual:@" "]) 18 | { 19 | AppDelegate *delegate = [[NSApplication sharedApplication] delegate]; 20 | [delegate togglePreviewPanel:self]; 21 | } 22 | else 23 | { 24 | [super keyDown:theEvent]; 25 | } 26 | } 27 | 28 | @end 29 | -------------------------------------------------------------------------------- /DMHY/View/NSTableView+ContextMenu.h: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+ContextMenu.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/22/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @protocol ContextMenuDelegate 12 | - (NSMenu*)tableView:(NSTableView*)aTableView menuForRows:(NSIndexSet*)rows; 13 | @end 14 | 15 | @interface NSTableView (ContextMenu) 16 | 17 | @end 18 | -------------------------------------------------------------------------------- /DMHY/View/NSTableView+ContextMenu.m: -------------------------------------------------------------------------------- 1 | // 2 | // NSTableView+ContextMenu.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 3/22/16. 6 | // Copyright © 2016 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "NSTableView+ContextMenu.h" 10 | 11 | @implementation NSTableView (ContextMenu) 12 | 13 | - (NSMenu*)menuForEvent:(NSEvent*)event 14 | { 15 | NSPoint location = [self convertPoint:[event locationInWindow] fromView:nil]; 16 | NSInteger row = [self rowAtPoint:location]; 17 | if (!(row >= 0) || ([event type] != NSRightMouseDown)) { 18 | return [super menuForEvent:event]; 19 | } 20 | NSIndexSet *selected = [self selectedRowIndexes]; 21 | if (![selected containsIndex:row]) { 22 | selected = [NSIndexSet indexSetWithIndex:row]; 23 | [self selectRowIndexes:selected byExtendingSelection:NO]; 24 | } 25 | if ([[self delegate] respondsToSelector:@selector(tableView:menuForRows:)]) { 26 | return [(id)[self delegate] tableView:self menuForRows:selected]; 27 | } 28 | return [super menuForEvent:event]; 29 | } 30 | 31 | @end 32 | -------------------------------------------------------------------------------- /DMHY/ViewController.h: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 15/8/30. 6 | // Copyright © 2015年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface ViewController : NSViewController 12 | 13 | 14 | @end 15 | 16 | -------------------------------------------------------------------------------- /DMHY/ViewController.m: -------------------------------------------------------------------------------- 1 | // 2 | // ViewController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 15/8/30. 6 | // Copyright © 2015年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "ViewController.h" 10 | #import "PreferenceController.h" 11 | #import "SitePreferenceController.h" 12 | #import "DMHYCoreDataStackManager.h" 13 | #import "TorrentItem.h" 14 | #import "TitleTableCellView.h" 15 | #import "NavigationView.h" 16 | #import "DMHYXMLDataManager.h" 17 | #import "DMHYJSONDataManager.h" 18 | #import "NSTableView+ContextMenu.h" 19 | #import 20 | #import "DMHYSiteChecker.h" 21 | 22 | /** 23 | * 加载数据步骤: 24 | 1. 判断当前站点返回是 XML 还是 JSON 数据 25 | 2. 调用相应的解析方法 26 | 3. 解析完毕,重新载入 tableview 27 | 28 | 下载动漫花园种子: 29 | 1. 获取到该条目的介绍页面 30 | 2. 解析介绍页面的 HTML DOM 用 XPath 提取出种子链接 31 | 3. 调用下载种子文件的方法(DMHYDownloader downloadTorrentWithURL:) 32 | 33 | */ 34 | @interface ViewController() 35 | 36 | @property (weak) IBOutlet NSTableView *tableView; 37 | @property (weak) IBOutlet NSProgressIndicator *indicator; 38 | 39 | @property (weak) IBOutlet NSTextField *searchTextField; 40 | @property (weak) IBOutlet NSTextField *info; 41 | 42 | @property (nonatomic, strong) NSMutableArray *torrents; 43 | @property (nonatomic, strong) NSString *searchURLString; 44 | 45 | @property (nonatomic, strong) DMHYSite *site; 46 | 47 | @property (nonatomic) BOOL isMagnetLink; 48 | @property (nonatomic) BOOL isSubKeyword; 49 | 50 | @property (nonatomic) NSInteger fetchInterval; 51 | @property (nonatomic) NSInteger currentRow; 52 | 53 | @property (nonatomic, strong) DMHYSiteChecker *checker; 54 | 55 | @end 56 | 57 | @implementation ViewController 58 | 59 | - (void)viewDidLoad { 60 | [super viewDidLoad]; 61 | [self observeNotification]; 62 | [self registeAppDefaults]; 63 | [[DMHYCoreDataStackManager sharedManager] seedDataIfNeed]; 64 | [self setupPreference]; 65 | [self setupTableViewStyle]; 66 | [self setupData:self]; 67 | [self setupRepeatTask]; 68 | [self setupMenuItems]; 69 | [self setupTableViewDoubleAction]; 70 | } 71 | 72 | - (void)setRepresentedObject:(id)representedObject { 73 | [super setRepresentedObject:representedObject]; 74 | } 75 | 76 | #pragma mark - Setup 77 | 78 | - (void)registeAppDefaults { 79 | NSDictionary *appDefaults = @{ FliterKeywordKey : @"", 80 | DontDownloadCollectionKey : @YES, 81 | kFetchInterval : @(15*60), 82 | kMainTableViewRowStyle :@2, 83 | kDownloadLinkType :@0, 84 | DMHYThemeKey : @1 }; 85 | [[NSUserDefaults standardUserDefaults] registerDefaults:appDefaults]; 86 | } 87 | 88 | - (void)setupTableViewStyle { 89 | self.tableView.rowSizeStyle = [self preferedRowSizeStyle]; 90 | self.tableView.usesAlternatingRowBackgroundColors = YES; 91 | self.tableView.columnAutoresizingStyle = NSTableViewUniformColumnAutoresizingStyle; 92 | [self.tableView sizeLastColumnToFit]; 93 | } 94 | 95 | - (NSTableViewRowSizeStyle )preferedRowSizeStyle { 96 | NSInteger rowStyle = [PreferenceController viewPreferenceTableViewRowStyle]; 97 | switch (rowStyle) { 98 | case 0: 99 | return NSTableViewRowSizeStyleSmall; 100 | case 1: 101 | return NSTableViewRowSizeStyleMedium; 102 | case 2: 103 | return NSTableViewRowSizeStyleLarge; 104 | default: 105 | break; 106 | } 107 | return NSTableViewRowSizeStyleSmall; 108 | } 109 | 110 | /** 111 | * Retrive saved preference value and set to self variable. 112 | */ 113 | - (void)setupPreference { 114 | self.isMagnetLink = [PreferenceController preferenceDownloadLinkType]; 115 | self.fetchInterval = [PreferenceController preferenceFetchInterval]; 116 | } 117 | 118 | /** 119 | * Configure data start point. 120 | * 121 | */ 122 | - (IBAction)setupData:(id)sender { 123 | //NSLog(@"sender class %@",[sender class]); 124 | if ([sender isKindOfClass:[ViewController class]]) { 125 | self.isSubKeyword = YES; 126 | } 127 | if (!self.isSubKeyword) { 128 | return; 129 | } 130 | [self resetData]; 131 | [self startAnimatingProgressIndicator]; 132 | [self configureSearchURLString]; 133 | [self.torrents removeAllObjects]; 134 | if ([self.site.responseType isEqualToString:@"json"]) { 135 | [[DMHYJSONDataManager manager] GET:self.searchURLString success:^(NSArray *objects) { 136 | self.torrents = [objects mutableCopy]; 137 | [self reloadDataAndStopIndicator]; 138 | } failure:^(NSError *error) { 139 | [self setErrorInfoAndStopIndicator]; 140 | }]; 141 | } else { 142 | [[DMHYXMLDataManager manager] GET:self.searchURLString success:^(NSArray *objects) { 143 | self.torrents = [objects mutableCopy]; 144 | [self reloadDataAndStopIndicator]; 145 | } failure:^(NSError *error) { 146 | [self setErrorInfoAndStopIndicator]; 147 | }]; 148 | } 149 | } 150 | 151 | #pragma mark - Indicator and reload table view data 152 | 153 | - (void)reloadDataAndStopIndicator { 154 | [self stopAnimatingProgressIndicator]; 155 | [self.tableView reloadData]; 156 | self.info.stringValue = @"加载完成 w"; 157 | } 158 | 159 | - (void)setErrorInfoAndStopIndicator { 160 | self.info.stringValue = @"网络错误喵?"; 161 | [self stopAnimatingProgressIndicator]; 162 | } 163 | 164 | /** 165 | * Set search url string to download site. 166 | */ 167 | - (void)configureSearchURLString { 168 | if ([self isLoadHomePage]) { 169 | NSString *siteMain = self.site.mainURL; 170 | self.searchURLString = siteMain; 171 | } else { 172 | NSString *siteSearch = self.site.searchURL; 173 | self.searchURLString = [[NSString stringWithFormat:siteSearch, self.searchTextField.stringValue] stringByAddingPercentEncodingWithAllowedCharacters:[NSCharacterSet URLQueryAllowedCharacterSet]]; 174 | } 175 | // NSLog(@"Site Info %@ %@ %@",self.site.name,self.site.responseType, self.site.isAutoDownload); 176 | } 177 | 178 | #pragma mark - Repeat Task 179 | 180 | /** 181 | * Schedule auto download new torrent task. 182 | */ 183 | - (void)setupRepeatTask { 184 | self.checker = [[DMHYSiteChecker alloc] init]; 185 | [self.checker startWitchCheckInterval:self.fetchInterval]; 186 | } 187 | 188 | #pragma mark - NSTableViewDataSource 189 | 190 | - (NSInteger)numberOfRowsInTableView:(NSTableView *)tableView { 191 | return self.torrents.count; 192 | } 193 | 194 | #pragma mark - NSTableViewDelegate 195 | 196 | - (NSView *)tableView:(NSTableView *)tableView viewForTableColumn:(NSTableColumn *)tableColumn row:(NSInteger)row { 197 | NSString *identifier = tableColumn.identifier; 198 | TorrentItem *torrent = self.torrents[row]; 199 | if ([identifier isEqualToString:@"pubDateCell"]) { 200 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"pubDateCell" owner:self]; 201 | cellView.textField.stringValue = torrent.pubDate; 202 | return cellView; 203 | } 204 | if ([identifier isEqualToString:@"titleCell"]) { 205 | TitleTableCellView *cellView = [tableView makeViewWithIdentifier:@"titleCell" owner:self]; 206 | cellView.textField.stringValue = torrent.title; 207 | return cellView; 208 | } 209 | if ([identifier isEqualToString:@"authorCell"]) { 210 | NSTableCellView *cellView = [tableView makeViewWithIdentifier:@"authorCell" owner:self]; 211 | cellView.textField.stringValue = torrent.author; 212 | return cellView; 213 | } 214 | return nil; 215 | } 216 | 217 | - (void)tableViewSelectionDidChange:(NSNotification *)notification { 218 | self.currentRow = self.tableView.selectedRow; 219 | } 220 | 221 | #pragma mark - Table View Context Menu Delegate 222 | 223 | - (NSMenu *)tableView:(NSTableView *)aTableView menuForRows:(NSIndexSet *)rows { 224 | NSMenu *rightClickMenu = [[NSMenu alloc] initWithTitle:@""]; 225 | NSMenuItem *openItem = [[NSMenuItem alloc] initWithTitle:@"打开介绍页面" 226 | action:@selector(openTorrentLink:) 227 | keyEquivalent:@""]; 228 | NSMenuItem *downloadItem = [[NSMenuItem alloc] initWithTitle:@"下载" 229 | action:@selector(queryDownloadURL:) 230 | keyEquivalent:@""]; 231 | [rightClickMenu addItem:openItem]; 232 | [rightClickMenu addItem:downloadItem]; 233 | return rightClickMenu; 234 | } 235 | 236 | #pragma mark - Notification 237 | 238 | - (void)observeNotification { 239 | [DMHYNotification addObserver:self selector:@selector(handleDownloadTypeChanged) name:DMHYDownloadLinkTypeNotification]; 240 | [DMHYNotification addObserver:self selector:@selector(handleAutoDownloadSiteChanged) name:DMHYAutoDownloadSiteChangedNotification]; 241 | [DMHYNotification addObserver:self selector:@selector(handleSelectKeywordChanged:) name:DMHYSelectKeywordChangedNotification]; 242 | [DMHYNotification addObserver:self selector:@selector(handleFetchIntervalChanged) name:DMHYFetchIntervalChangedNotification]; 243 | [DMHYNotification addObserver:self selector:@selector(handleThemeChanged) name:DMHYThemeChangedNotification]; 244 | [DMHYNotification addObserver:self selector:@selector(handleDoubleActionChanged) name:DMHYDoubleActionChangedNotification]; 245 | [DMHYNotification addObserver:self selector:@selector(handleMainTableViewRowStyleChanged) name:DMHYMainTableViewRowStyleChangedNotification]; 246 | [DMHYNotification addObserver:self selector:@selector(handleDontDownloadCollectionChanged) name:DMHYDontDownloadCollectionKeyDidChangedNotification]; 247 | [DMHYNotification addObserver:self selector:@selector(handleSearchSiteChanged:) name:DMHYSearchSiteChangedNotification]; 248 | [DMHYNotification addObserver:self selector:@selector(handleKeywordChecked:) name:DMHYKeywordCheckedNotification]; 249 | } 250 | 251 | - (void)handleKeywordChecked:(NSNotification *)notification { 252 | NSDictionary *userInfo = notification.userInfo; 253 | NSString *infoText = [NSString stringWithFormat:@"%@ %@ 检查完毕", userInfo[@"site"], userInfo[@"keyword"]]; 254 | self.info.stringValue = infoText; 255 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ 256 | NSDate *time = [NSDate new]; 257 | self.info.stringValue = [NSString stringWithFormat:@" 上次检查时间 %@",[[DMHYTool tool] infoDateStringFromDate:time]]; 258 | }); 259 | } 260 | 261 | - (void)handleSearchSiteChanged:(NSNotification *)notification { 262 | DMHYSite *site = notification.object; 263 | self.site = site; 264 | [self setupData:self]; 265 | } 266 | 267 | - (void)resetData { 268 | [self.torrents removeAllObjects]; 269 | [self.tableView reloadData]; 270 | } 271 | 272 | - (void)handleAutoDownloadSiteChanged { 273 | [self.checker invalidateTimer]; 274 | self.fetchInterval = [PreferenceController preferenceFetchInterval]; 275 | [self setupRepeatTask]; 276 | } 277 | 278 | - (void)handleDownloadTypeChanged { 279 | [self setupPreference]; 280 | } 281 | 282 | - (void)handleDontDownloadCollectionChanged { 283 | [self setupPreference]; 284 | } 285 | 286 | - (void)handleSelectKeywordChanged:(NSNotification *)noti { 287 | NSDictionary *userInfo = noti.userInfo; 288 | 289 | NSString *keywordStr = userInfo[kSelectKeyword]; 290 | BOOL isSubKeywordBOOL = [userInfo[kSelectKeywordIsSubKeyword] boolValue]; 291 | 292 | self.isSubKeyword = isSubKeywordBOOL; 293 | if (!isSubKeywordBOOL) { 294 | self.searchTextField.stringValue = @""; 295 | } else { 296 | self.searchTextField.stringValue = keywordStr; 297 | } 298 | 299 | [self setupData:userInfo]; 300 | } 301 | 302 | - (void)handleDoubleActionChanged { 303 | [self setupTableViewDoubleAction]; 304 | } 305 | 306 | - (void)setupTableViewDoubleAction { 307 | NSInteger action = [PreferenceController preferenceDoubleAction]; 308 | switch (action) { 309 | case 0: 310 | self.tableView.doubleAction = @selector(openTorrentLink:); 311 | break; 312 | case 1: 313 | self.tableView.doubleAction = @selector(queryDownloadURL:); 314 | break; 315 | default: 316 | break; 317 | } 318 | 319 | } 320 | 321 | - (void)handleFetchIntervalChanged { 322 | [self.checker invalidateTimer]; 323 | self.fetchInterval = [PreferenceController preferenceFetchInterval]; 324 | [self setupRepeatTask]; 325 | } 326 | 327 | - (void)handleThemeChanged { 328 | [self.view setNeedsDisplay:YES]; 329 | } 330 | 331 | - (void)handleMainTableViewRowStyleChanged { 332 | [self setupTableViewStyle]; 333 | [self.tableView setNeedsDisplay:YES]; 334 | } 335 | 336 | #pragma mark - Property Initialization 337 | 338 | - (NSMutableArray *)torrents { 339 | if (!_torrents) { 340 | _torrents = [NSMutableArray array]; 341 | } 342 | return _torrents; 343 | } 344 | 345 | - (DMHYSite *)site { 346 | if (!_site) { 347 | _site = [[DMHYCoreDataStackManager sharedManager] currentUseSite]; 348 | } 349 | return _site; 350 | } 351 | 352 | - (NSString *)searchURLString { 353 | if (!_searchURLString) { 354 | _searchURLString = [NSString new]; 355 | } 356 | return _searchURLString; 357 | } 358 | 359 | /** 360 | * 判断搜索关键字是否为空,如果为空的话加载主页,否则加载搜索页 361 | * 362 | * @return YES 加载主页数据 363 | */ 364 | - (BOOL)isLoadHomePage { 365 | return [self.searchTextField.stringValue isEqualToString:@""] ? YES : NO; 366 | } 367 | 368 | #pragma mark - Download 369 | 370 | /** 371 | * Query clicked row torrent download url then download torrent. 372 | * 373 | * @param sender 374 | */ 375 | - (IBAction)queryDownloadURL:(id)sender { 376 | NSInteger row = -1; 377 | if ([sender isKindOfClass:[NSButton class]]) { 378 | row = [self.tableView rowForView:sender]; 379 | } else { 380 | row = self.currentRow; 381 | } 382 | [self startAnimatingProgressIndicator]; 383 | TorrentItem *item = (TorrentItem *)[self.torrents objectAtIndex:row]; 384 | if ([self.site.name isEqualToString:@"dmhy"]) { 385 | if (!self.isMagnetLink) { 386 | [[DMHYDownloader downloader] downloadTorrentFromPageURLString:item.link.absoluteString willStartBlock:^{ 387 | [self startAnimatingProgressIndicator]; 388 | } success:^{ 389 | dispatch_async(dispatch_get_main_queue(), ^{ 390 | [self stopAnimatingProgressIndicator]; 391 | }); 392 | } failure:^(NSError *error) { 393 | NSLog(@"Error %@",[error localizedDescription]); 394 | [self setErrorInfoAndStopIndicator]; 395 | }]; 396 | return; 397 | } 398 | } 399 | if ([self.site.name containsString:@"nyaa.se"]) { 400 | [[DMHYDownloader downloader] downloadTorrentWithURL:item.link]; 401 | [self stopAnimatingProgressIndicator]; 402 | return; 403 | } 404 | // acg.rip contains .torrent bt.acg.gg contains down.php 405 | if ([item.magnet.absoluteString containsString:@".torrent"] || 406 | [item.magnet.absoluteString containsString:@"down.php"]) { 407 | [[DMHYDownloader downloader] downloadTorrentWithURL:item.magnet]; 408 | [self stopAnimatingProgressIndicator]; 409 | return; 410 | } 411 | if ([item.magnet.absoluteString containsString:@"magnet:?xt=urn:btih:"]) { 412 | [self stopAnimatingProgressIndicator]; 413 | [self openMagnetWith:item.magnet]; 414 | return; 415 | } 416 | } 417 | 418 | - (void)openMagnetWith:(NSURL *)magnet { 419 | [[NSWorkspace sharedWorkspace] openURL:magnet]; 420 | } 421 | 422 | 423 | #pragma mark - NSTextFieldDelegate 424 | 425 | - (void)controlTextDidEndEditing:(NSNotification *)notification { 426 | if ([notification.userInfo[@"NSTextMovement"] intValue] == NSReturnTextMovement) { 427 | [self setupData:self]; 428 | } 429 | } 430 | 431 | 432 | #pragma mark - ProgressIndicator 433 | 434 | - (void)startAnimatingProgressIndicator { 435 | [self.indicator startAnimation:self]; 436 | self.info.stringValue = @""; 437 | } 438 | 439 | - (void)stopAnimatingProgressIndicator { 440 | [self.indicator stopAnimation:self]; 441 | } 442 | 443 | #pragma mark - Utils 444 | 445 | /** 446 | * Open Torrent Description Page in default browser. 447 | * 448 | * @param sender 449 | */ 450 | - (void)openTorrentLink:(id) sender { 451 | NSInteger rowIndex = self.currentRow; 452 | if (rowIndex < 0) { 453 | //Click column header do nothing. 454 | return; 455 | } 456 | TorrentItem *item = self.torrents[rowIndex]; 457 | [[NSWorkspace sharedWorkspace] openURL:item.link]; 458 | } 459 | 460 | - (void)setupAutomaticDownloadNewTorrent { 461 | [self.checker automaticDownloadTorrent]; 462 | 463 | } 464 | 465 | - (void)setupMenuItems { 466 | NSMenu *mainMenu = [[NSApplication sharedApplication] mainMenu]; 467 | NSMenuItem *fileMenuItem = [mainMenu itemWithTitle:@"File"]; 468 | NSMenu *fileSubMenu = [fileMenuItem submenu]; 469 | // 设置快捷键时默认 mask 是 command,然后加单键使用小写字母(如:@“r” == cmd+r),想要多加个 shift mask,用大写字母。(如:@“R” == cmd+shift+r) 470 | NSMenuItem *menuRefreshSubKeywordMenuItem = [[NSMenuItem alloc] initWithTitle:@"手动检查订阅更新" 471 | action:@selector(setupAutomaticDownloadNewTorrent) 472 | keyEquivalent:@"r"]; 473 | [fileSubMenu addItem:menuRefreshSubKeywordMenuItem]; 474 | 475 | } 476 | 477 | @end 478 | -------------------------------------------------------------------------------- /DMHY/ViewerPreferences.tiff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/ViewerPreferences.tiff -------------------------------------------------------------------------------- /DMHY/WindowController.h: -------------------------------------------------------------------------------- 1 | // 2 | // WindowController.h 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 16/3/7. 6 | // Copyright © 2016年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | @interface WindowController : NSWindowController 12 | 13 | @end 14 | -------------------------------------------------------------------------------- /DMHY/WindowController.m: -------------------------------------------------------------------------------- 1 | // 2 | // WindowController.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 16/3/7. 6 | // Copyright © 2016年 yaqinking. All rights reserved. 7 | // 8 | 9 | #import "WindowController.h" 10 | 11 | @interface WindowController () 12 | 13 | @end 14 | 15 | @implementation WindowController 16 | 17 | - (void)windowDidLoad { 18 | [super windowDidLoad]; 19 | 20 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 21 | // Set to 10.10 Yosemite style window. 22 | self.window.titleVisibility = NSWindowTitleHidden; 23 | self.window.styleMask |= NSFullSizeContentViewWindowMask; 24 | self.window.titlebarAppearsTransparent = YES; 25 | } 26 | 27 | @end 28 | -------------------------------------------------------------------------------- /DMHY/download.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/yaqinking/DMHY/82e3ef3d3f2b7a48a56ae8554053e65331cf3268/DMHY/download.png -------------------------------------------------------------------------------- /DMHY/main.m: -------------------------------------------------------------------------------- 1 | // 2 | // main.m 3 | // DMHY 4 | // 5 | // Created by 小笠原やきん on 9/9/15. 6 | // Copyright © 2015 yaqinking. All rights reserved. 7 | // 8 | 9 | #import 10 | 11 | int main(int argc, const char * argv[]) { 12 | return NSApplicationMain(argc, argv); 13 | } 14 | -------------------------------------------------------------------------------- /DMHY/support-site.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "yaqinking", 3 | "create_at": "2016-07-16", 4 | "sites": [ 5 | { 6 | "site_name": "dmhy", 7 | "main_url": "https://share.dmhy.org/topics/rss/rss.xml", 8 | "search_url": "https://share.dmhy.org/topics/rss/rss.xml?keyword=%@", 9 | "fliter_site": false, 10 | "auto_download": true, 11 | "download_type": "torrent", 12 | "download_fin": false, 13 | "response_type": "xml", 14 | "current_use": true, 15 | "keywords": {} 16 | }, 17 | { 18 | "site_name": "acg.rip", 19 | "main_url": "https://acg.rip/.xml", 20 | "search_url": "https://acg.rip/.xml?term=%@", 21 | "fliter_site": false, 22 | "auto_download": false, 23 | "download_type": "torrent", 24 | "download_fin": false, 25 | "response_type": "xml", 26 | "current_use": false, 27 | "keywords": {} 28 | }, 29 | { 30 | "site_name": "bangumi.moe", 31 | "main_url": "https://bangumi.moe/api/v2/torrent/page/1?limit=50", 32 | "search_url": "https://bangumi.moe/api/v2/torrent/search?limit=50&p=1&query=%@", 33 | "fliter_site": false, 34 | "auto_download": false, 35 | "download_type": "magnet", 36 | "download_fin": false, 37 | "response_type": "json", 38 | "current_use": false, 39 | "keywords": {} 40 | }, 41 | { 42 | "site_name": "acg.gg", 43 | "main_url": "http://bt.acg.gg/rss.xml", 44 | "search_url": "http://bt.acg.gg/rss.xml?keyword=%@", 45 | "fliter_site": false, 46 | "auto_download": false, 47 | "download_type": "torrent", 48 | "download_fin": false, 49 | "response_type": "xml", 50 | "current_use": false, 51 | "keywords": {} 52 | }, 53 | { 54 | "site_name": "kisssub.org", 55 | "main_url": "http://www.kisssub.org/rss.xml", 56 | "search_url": "http://www.kisssub.org/rss-%@.xml", 57 | "fliter_site": false, 58 | "auto_download": false, 59 | "download_type": "torrent", 60 | "download_fin": false, 61 | "response_type": "xml", 62 | "current_use": false, 63 | "keywords": {} 64 | }, 65 | { 66 | "site_name": "mikanani.me", 67 | "main_url": "http://mikanani.me/RSS/Classic", 68 | "search_url": "http://mikanani.me/RSS/Search?searchstr=%@", 69 | "fliter_site": false, 70 | "auto_download": false, 71 | "download_type": "magnet", 72 | "download_fin": false, 73 | "response_type": "xml", 74 | "current_use": false, 75 | "keywords": {} 76 | }, 77 | { 78 | "site_name": "nyaa.si", 79 | "main_url": "http://www.nyaa.si/?page=rss", 80 | "search_url": "http://www.nyaa.si/?page=rss&term=%@", 81 | "fliter_site": false, 82 | "auto_download": false, 83 | "download_type": "torrent", 84 | "download_fin": false, 85 | "response_type": "xml", 86 | "current_use": false, 87 | "keywords": {} 88 | } 89 | ] 90 | } 91 | -------------------------------------------------------------------------------- /DMHY/zh-Hans.lproj/Main.strings: -------------------------------------------------------------------------------- 1 | 2 | /* Class = "NSMenuItem"; title = "DMHY"; ObjectID = "1Xt-HY-uBw"; */ 3 | "1Xt-HY-uBw.title" = "DMHY"; 4 | 5 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "3Zv-Lh-hMH"; */ 6 | "3Zv-Lh-hMH.title" = "Table View Cell"; 7 | 8 | /* Class = "NSMenuItem"; title = "Quit DMHY"; ObjectID = "4sb-4s-VLi"; */ 9 | "4sb-4s-VLi.title" = "Quit DMHY"; 10 | 11 | /* Class = "NSMenuItem"; title = "About DMHY"; ObjectID = "5kV-Vb-QxS"; */ 12 | "5kV-Vb-QxS.title" = "About DMHY"; 13 | 14 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "6DS-Xm-Q9b"; */ 15 | "6DS-Xm-Q9b.title" = "Table View Cell"; 16 | 17 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "6jV-1H-fgd"; */ 18 | "6jV-1H-fgd.title" = "Table View Cell"; 19 | 20 | /* Class = "NSTextFieldCell"; placeholderString = "输入要搜索的关键字 >_<"; ObjectID = "76V-ZO-pEU"; */ 21 | "76V-ZO-pEU.placeholderString" = "输入要搜索的关键字 >_<"; 22 | 23 | /* Class = "NSMenuItem"; title = "Fetch All Torrents Info"; ObjectID = "8pT-dL-euw"; */ 24 | "8pT-dL-euw.title" = "Fetch All Torrents Info"; 25 | 26 | /* Class = "NSMenuItem"; title = "Enter Full Screen"; ObjectID = "9jA-f7-reS"; */ 27 | "9jA-f7-reS.title" = "Enter Full Screen"; 28 | 29 | /* Class = "NSMenuItem"; title = "打开下载到的目录"; ObjectID = "9vj-Xu-N63"; */ 30 | "9vj-Xu-N63.title" = "打开下载到的目录"; 31 | 32 | /* Class = "NSMenu"; title = "Main Menu"; ObjectID = "AYu-sK-qS6"; */ 33 | "AYu-sK-qS6.title" = "Main Menu"; 34 | 35 | /* Class = "NSTableColumn"; headerCell.title = "文件名"; ObjectID = "BGh-WO-WlU"; */ 36 | "BGh-WO-WlU.headerCell.title" = "文件名"; 37 | 38 | /* Class = "NSMenuItem"; title = "Preferences…"; ObjectID = "BOF-NM-1cW"; */ 39 | "BOF-NM-1cW.title" = "Preferences…"; 40 | 41 | /* Class = "NSButtonCell"; title = "添加所有"; ObjectID = "BfY-Rp-ZIt"; */ 42 | "BfY-Rp-ZIt.title" = "添加所有"; 43 | 44 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "CLH-bM-NEE"; */ 45 | "CLH-bM-NEE.title" = "Table View Cell"; 46 | 47 | /* Class = "NSMenuItem"; title = "Paste and Match Style"; ObjectID = "Cdz-Vl-Cgb"; */ 48 | "Cdz-Vl-Cgb.title" = "Paste and Match Style"; 49 | 50 | /* Class = "NSViewController"; title = "添加关键字"; ObjectID = "DTa-6Z-o28"; */ 51 | "DTa-6Z-o28.title" = "添加关键字"; 52 | 53 | /* Class = "NSMenuItem"; title = "关闭"; ObjectID = "DVo-aG-piG"; */ 54 | "DVo-aG-piG.title" = "关闭"; 55 | 56 | /* Class = "NSMenu"; title = "Help"; ObjectID = "F2S-fz-NVQ"; */ 57 | "F2S-fz-NVQ.title" = "Help"; 58 | 59 | /* Class = "NSMenuItem"; title = "DMHY Help"; ObjectID = "FKE-Sm-Kum"; */ 60 | "FKE-Sm-Kum.title" = "DMHY Help"; 61 | 62 | /* Class = "NSButtonCell"; title = "添加"; ObjectID = "GhW-r2-L8I"; */ 63 | "GhW-r2-L8I.title" = "添加"; 64 | 65 | /* Class = "NSMenuItem"; title = "View"; ObjectID = "H8h-7b-M4v"; */ 66 | "H8h-7b-M4v.title" = "View"; 67 | 68 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "HE2-g0-k6g"; */ 69 | "HE2-g0-k6g.title" = "Table View Cell"; 70 | 71 | /* Class = "NSTableColumn"; headerCell.title = "已保存的关键字"; ObjectID = "HdH-We-VzC"; */ 72 | "HdH-We-VzC.headerCell.title" = "已保存的关键字"; 73 | 74 | /* Class = "NSTableColumn"; headerCell.title = "番组名(中文)"; ObjectID = "HeL-Kb-HRu"; */ 75 | "HeL-Kb-HRu.headerCell.title" = "番组名(中文)"; 76 | 77 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Heg-jy-XeK"; */ 78 | "Heg-jy-XeK.title" = "Text Cell"; 79 | 80 | /* Class = "NSMenu"; title = "View"; ObjectID = "HyV-fh-RgO"; */ 81 | "HyV-fh-RgO.title" = "View"; 82 | 83 | /* Class = "NSWindow"; title = "动漫花园"; ObjectID = "IQv-IB-iLA"; */ 84 | "IQv-IB-iLA.title" = "动漫花园"; 85 | 86 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "Ihb-Ab-kA6"; */ 87 | "Ihb-Ab-kA6.title" = "Table View Cell"; 88 | 89 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "Jai-d0-HJP"; */ 90 | "Jai-d0-HJP.title" = "Table View Cell"; 91 | 92 | /* Class = "NSTableColumn"; headerCell.title = "字幕组"; ObjectID = "KFF-NT-9Uh"; */ 93 | "KFF-NT-9Uh.headerCell.title" = "字幕组"; 94 | 95 | /* Class = "NSMenuItem"; title = "Capitalize"; ObjectID = "KVi-bH-A71"; */ 96 | "KVi-bH-A71.title" = "Capitalize"; 97 | 98 | /* Class = "NSMenuItem"; title = "Speech"; ObjectID = "Kbv-ou-V1b"; */ 99 | "Kbv-ou-V1b.title" = "Speech"; 100 | 101 | /* Class = "NSMenuItem"; title = "Show All"; ObjectID = "Kd2-mp-pUS"; */ 102 | "Kd2-mp-pUS.title" = "Show All"; 103 | 104 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "LBn-16-CNw"; */ 105 | "LBn-16-CNw.title" = "Text Cell"; 106 | 107 | /* Class = "NSMenuItem"; title = "Bring All to Front"; ObjectID = "LE2-aR-0XJ"; */ 108 | "LE2-aR-0XJ.title" = "Bring All to Front"; 109 | 110 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Ldy-r6-eog"; */ 111 | "Ldy-r6-eog.title" = "Text Cell"; 112 | 113 | /* Class = "NSMenuItem"; title = "Services"; ObjectID = "NMo-om-nkz"; */ 114 | "NMo-om-nkz.title" = "Services"; 115 | 116 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "O8D-nH-MrJ"; */ 117 | "O8D-nH-MrJ.title" = "Text Cell"; 118 | 119 | /* Class = "NSMenuItem"; title = "Minimize"; ObjectID = "OY7-WF-poV"; */ 120 | "OY7-WF-poV.title" = "Minimize"; 121 | 122 | /* Class = "NSButtonCell"; title = "搜索"; ObjectID = "Oa0-ov-WIx"; */ 123 | "Oa0-ov-WIx.title" = "搜索"; 124 | 125 | /* Class = "NSMenuItem"; title = "Hide DMHY"; ObjectID = "Olw-nP-bQN"; */ 126 | "Olw-nP-bQN.title" = "Hide DMHY"; 127 | 128 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "Q1g-L3-sgY"; */ 129 | "Q1g-L3-sgY.title" = "Table View Cell"; 130 | 131 | /* Class = "NSMenu"; title = "Edit"; ObjectID = "Qb6-ez-idR"; */ 132 | "Qb6-ez-idR.title" = "Edit"; 133 | 134 | /* Class = "NSMenuItem"; title = "Zoom"; ObjectID = "R4o-n2-Eq4"; */ 135 | "R4o-n2-Eq4.title" = "Zoom"; 136 | 137 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "RgY-0l-DSE"; */ 138 | "RgY-0l-DSE.title" = "Text Cell"; 139 | 140 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "RsV-x3-fOH"; */ 141 | "RsV-x3-fOH.title" = "Table View Cell"; 142 | 143 | /* Class = "NSMenu"; title = "Window"; ObjectID = "Td7-aD-5lo"; */ 144 | "Td7-aD-5lo.title" = "Window"; 145 | 146 | /* Class = "NSTableColumn"; headerCell.title = "上传者"; ObjectID = "Uhs-2l-q8U"; */ 147 | "Uhs-2l-q8U.headerCell.title" = "上传者"; 148 | 149 | /* Class = "NSMenuItem"; title = "Hide Others"; ObjectID = "Vdr-fp-XzO"; */ 150 | "Vdr-fp-XzO.title" = "Hide Others"; 151 | 152 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Wp0-Bo-uvV"; */ 153 | "Wp0-Bo-uvV.title" = "Text Cell"; 154 | 155 | /* Class = "NSMenuItem"; title = "Transformations"; ObjectID = "X3Q-on-iSx"; */ 156 | "X3Q-on-iSx.title" = "Transformations"; 157 | 158 | /* Class = "NSMenuItem"; title = "Make Lower Case"; ObjectID = "XAT-Mt-tUi"; */ 159 | "XAT-Mt-tUi.title" = "Make Lower Case"; 160 | 161 | /* Class = "NSTextFieldCell"; title = "Label"; ObjectID = "XgS-oG-Dbw"; */ 162 | "XgS-oG-Dbw.title" = "Label"; 163 | 164 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "YGo-B1-dFu"; */ 165 | "YGo-B1-dFu.title" = "Text Cell"; 166 | 167 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "Yfq-gQ-Oxs"; */ 168 | "Yfq-gQ-Oxs.title" = "Text Cell"; 169 | 170 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "ZAl-C1-BWZ"; */ 171 | "ZAl-C1-BWZ.title" = "Text Cell"; 172 | 173 | /* Class = "NSMenuItem"; title = "Start Speaking"; ObjectID = "ZaZ-s6-4Ix"; */ 174 | "ZaZ-s6-4Ix.title" = "Start Speaking"; 175 | 176 | /* Class = "NSMenuItem"; title = "Window"; ObjectID = "aUF-d1-5bR"; */ 177 | "aUF-d1-5bR.title" = "Window"; 178 | 179 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "as8-GI-Rmc"; */ 180 | "as8-GI-Rmc.title" = "Table View Cell"; 181 | 182 | /* Class = "NSTableColumn"; headerCell.title = "搜索关键字"; ObjectID = "baj-mN-cht"; */ 183 | "baj-mN-cht.headerCell.title" = "搜索关键字"; 184 | 185 | /* Class = "NSMenu"; title = "File"; ObjectID = "bib-Uj-vzu"; */ 186 | "bib-Uj-vzu.title" = "File"; 187 | 188 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "bti-xX-z1h"; */ 189 | "bti-xX-z1h.title" = "Table View Cell"; 190 | 191 | /* Class = "NSTableColumn"; headerCell.title = "标题"; ObjectID = "buQ-50-SAf"; */ 192 | "buQ-50-SAf.headerCell.title" = "标题"; 193 | 194 | /* Class = "NSMenuItem"; title = "File"; ObjectID = "dMs-cI-mzQ"; */ 195 | "dMs-cI-mzQ.title" = "File"; 196 | 197 | /* Class = "NSMenu"; title = "Speech"; ObjectID = "dRL-wg-o1f"; */ 198 | "dRL-wg-o1f.title" = "Speech"; 199 | 200 | /* Class = "NSButtonCell"; title = "获取当前季度关键字"; ObjectID = "dln-kk-c6S"; */ 201 | "dln-kk-c6S.title" = "获取当前季度关键字"; 202 | 203 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "fmX-ho-FMH"; */ 204 | "fmX-ho-FMH.title" = "Text Cell"; 205 | 206 | /* Class = "NSMenuItem"; title = "Select All"; ObjectID = "frq-UF-PSA"; */ 207 | "frq-UF-PSA.title" = "Select All"; 208 | 209 | /* Class = "NSMenuItem"; title = "Copy"; ObjectID = "g6i-Wg-9XK"; */ 210 | "g6i-Wg-9XK.title" = "Copy"; 211 | 212 | /* Class = "NSTableColumn"; headerCell.title = "上传日期"; ObjectID = "ghi-PI-mzL"; */ 213 | "ghi-PI-mzL.headerCell.title" = "上传日期"; 214 | 215 | /* Class = "NSButtonCell"; title = "添加选中"; ObjectID = "gui-5b-wR1"; */ 216 | "gui-5b-wR1.title" = "添加选中"; 217 | 218 | /* Class = "NSTextFieldCell"; placeholderString = "输入要保存的关键字"; ObjectID = "hLY-pA-HcS"; */ 219 | "hLY-pA-HcS.placeholderString" = "输入要保存的关键字"; 220 | 221 | /* Class = "NSMenuItem"; title = "Cut"; ObjectID = "hVE-L4-lmD"; */ 222 | "hVE-L4-lmD.title" = "Cut"; 223 | 224 | /* Class = "NSMenu"; title = "Services"; ObjectID = "hz9-B4-Xy5"; */ 225 | "hz9-B4-Xy5.title" = "Services"; 226 | 227 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "ig0-IQ-x2A"; */ 228 | "ig0-IQ-x2A.title" = "Table View Cell"; 229 | 230 | /* Class = "NSTextFieldCell"; title = "Table View Cell"; ObjectID = "j0c-XP-MwU"; */ 231 | "j0c-XP-MwU.title" = "Table View Cell"; 232 | 233 | /* Class = "NSMenuItem"; title = "Paste"; ObjectID = "jxD-yS-vdc"; */ 234 | "jxD-yS-vdc.title" = "Paste"; 235 | 236 | /* Class = "NSTableColumn"; headerCell.title = "修改日期"; ObjectID = "lUj-K9-oGh"; */ 237 | "lUj-K9-oGh.headerCell.title" = "修改日期"; 238 | 239 | /* Class = "NSTableColumn"; headerCell.title = "是否为新种子"; ObjectID = "mf7-I2-ees"; */ 240 | "mf7-I2-ees.headerCell.title" = "是否为新种子"; 241 | 242 | /* Class = "NSTableColumn"; headerCell.title = "日期"; ObjectID = "n4V-ZQ-v2F"; */ 243 | "n4V-ZQ-v2F.headerCell.title" = "日期"; 244 | 245 | /* Class = "NSTableColumn"; headerCell.title = "标题"; ObjectID = "nCr-UM-8pI"; */ 246 | "nCr-UM-8pI.headerCell.title" = "标题"; 247 | 248 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "p5N-JK-gJp"; */ 249 | "p5N-JK-gJp.title" = "Text Cell"; 250 | 251 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "qQb-1K-C0s"; */ 252 | "qQb-1K-C0s.title" = "Text Cell"; 253 | 254 | /* Class = "NSTableColumn"; headerCell.title = "周几"; ObjectID = "qle-b3-qU6"; */ 255 | "qle-b3-qU6.headerCell.title" = "周几"; 256 | 257 | /* Class = "NSMenuItem"; title = "Delete"; ObjectID = "sRO-AN-1gR"; */ 258 | "sRO-AN-1gR.title" = "Delete"; 259 | 260 | /* Class = "NSMenuItem"; title = "Edit"; ObjectID = "sUe-Si-5e1"; */ 261 | "sUe-Si-5e1.title" = "Edit"; 262 | 263 | /* Class = "NSTableColumn"; headerCell.title = "番组名(日文)"; ObjectID = "tAH-lS-QUg"; */ 264 | "tAH-lS-QUg.headerCell.title" = "番组名(日文)"; 265 | 266 | /* Class = "NSMenuItem"; title = "初始化自动下载种子数据库"; ObjectID = "twB-iS-wTW"; */ 267 | "twB-iS-wTW.title" = "初始化自动下载种子数据库"; 268 | 269 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "tzK-dj-QHS"; */ 270 | "tzK-dj-QHS.title" = "Text Cell"; 271 | 272 | /* Class = "NSMenu"; title = "DMHY"; ObjectID = "uQy-DD-JDr"; */ 273 | "uQy-DD-JDr.title" = "DMHY"; 274 | 275 | /* Class = "NSMenuItem"; title = "添加季度关键字"; ObjectID = "wJR-J2-GEX"; */ 276 | "wJR-J2-GEX.title" = "添加季度关键字"; 277 | 278 | /* Class = "NSMenuItem"; title = "Switch Theme"; ObjectID = "wM1-cj-jaY"; */ 279 | "wM1-cj-jaY.title" = "Switch Theme"; 280 | 281 | /* Class = "NSMenuItem"; title = "Help"; ObjectID = "wpr-3q-Mcd"; */ 282 | "wpr-3q-Mcd.title" = "Help"; 283 | 284 | /* Class = "NSMenuItem"; title = "Make Upper Case"; ObjectID = "x3i-38-mq2"; */ 285 | "x3i-38-mq2.title" = "Make Upper Case"; 286 | 287 | /* Class = "NSMenu"; title = "Transformations"; ObjectID = "xpR-Wl-Fcg"; */ 288 | "xpR-Wl-Fcg.title" = "Transformations"; 289 | 290 | /* Class = "NSMenuItem"; title = "Stop Speaking"; ObjectID = "xqg-b4-4uR"; */ 291 | "xqg-b4-4uR.title" = "Stop Speaking"; 292 | 293 | /* Class = "NSTextFieldCell"; title = "Text Cell"; ObjectID = "ycD-0e-CBH"; */ 294 | "ycD-0e-CBH.title" = "Text Cell"; 295 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 小笠原 やきん(yaqinking) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | # Uncomment this line to define a global platform for your project 2 | platform :osx, '10.10' 3 | 4 | target 'DMHY' do 5 | 6 | pod 'AFNetworking', '~> 3.0' 7 | pod 'Ono' 8 | pod 'CDEvents' 9 | pod 'DateTools' 10 | 11 | end 12 | 13 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - AFNetworking (3.1.0): 3 | - AFNetworking/NSURLSession (= 3.1.0) 4 | - AFNetworking/Reachability (= 3.1.0) 5 | - AFNetworking/Security (= 3.1.0) 6 | - AFNetworking/Serialization (= 3.1.0) 7 | - AFNetworking/UIKit (= 3.1.0) 8 | - AFNetworking/NSURLSession (3.1.0): 9 | - AFNetworking/Reachability 10 | - AFNetworking/Security 11 | - AFNetworking/Serialization 12 | - AFNetworking/Reachability (3.1.0) 13 | - AFNetworking/Security (3.1.0) 14 | - AFNetworking/Serialization (3.1.0) 15 | - CDEvents (1.2.2) 16 | - DateTools (1.7.0) 17 | - Ono (1.2.2) 18 | 19 | DEPENDENCIES: 20 | - AFNetworking (~> 3.0) 21 | - CDEvents 22 | - DateTools 23 | - Ono 24 | 25 | SPEC REPOS: 26 | trunk: 27 | - AFNetworking 28 | - CDEvents 29 | - DateTools 30 | - Ono 31 | 32 | SPEC CHECKSUMS: 33 | AFNetworking: 5e0e199f73d8626b11e79750991f5d173d1f8b67 34 | CDEvents: f311f23de13414f4b3625f1a663b2e1d9a85d982 35 | DateTools: 53288ee8b905fdc75897a1e6b5cc0144b14cba60 36 | Ono: 80b1c686777d3f6d5d5ce6fcac8a74afef4ca197 37 | 38 | PODFILE CHECKSUM: cdeda80e5c313d6f68f1c2a7f581fe7906f52591 39 | 40 | COCOAPODS: 1.10.1 41 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DMHY 2 | 一个订阅关键字后自动从相应站点下载新番的工具。 3 | 4 | 使用手册见 [Wiki](https://github.com/yaqinking/DMHY/wiki) 5 | 6 | 个人使用方法:把每天要看动画的关键字+字幕组+字幕类型设定好之后,每隔 60 分钟让她自动检测是否有新 ep。 7 | 8 | 软件运行系统需求: 9 | - 系统最低支持 OS X 10.10 10 | - 支持 Intel 和 Apple Silicon 处理器的 Mac 11 | 12 | 目前 13 | 14 | 支持以下站点搜索和自动下载种子或者使用 bt 客户端打开 magnet 链接 15 | 16 | - https://share.dmhy.org 17 | - https://acg.rip 18 | - http://bt.acg.gg 19 | - http://www.kisssub.org 20 | - http://mikanani.me 21 | - http://www.nyaa.si (提示:由于 nyaa 域名进行过更改,如果不是第一次使用本 app 需要重置数据库后才会更改为新域名。) 22 | 23 | 支持以下站点搜索 24 | 25 | - https://bangumi.moe 26 | 27 | 其它 RSS 站点如果支持类似 `https://share.dmhy.org/topics/rss/rss.xml?keyword=%@` 这种格式的可以尝试一下添加自定义站点看是否能够正常使用。(`%@` 代表搜索关键字) 28 | 29 | 功能: 30 | 31 | - 自动下载当天订阅的关键字更新 32 | - 导入关键字(方便批量添加,导入时如果站点不存在会添加为自定义站点) 33 | - 导出关键字(方便备份,分享给别人) 34 | - 文件管理(方便双击直接打开观看) 35 | 36 | 附加说明: 37 | 38 | - 使用 Sandbox 39 | - 使用 Apple Notarization 40 | 41 | ## Screenshot 42 | 暗色主题 43 | 44 | ![Imgur](http://i.imgur.com/engeN87.jpg) 45 | 46 | 多站点自动下载开关设置 47 | 48 | ![Imgur](http://i.imgur.com/R6IAD2Z.jpg) 49 | 50 | 文件管理 51 | 52 | ![Imgur](http://i.imgur.com/XUgmRkl.jpg) 53 | 54 | 导出关键字 55 | 56 | ![Imgur](http://i.imgur.com/INkputg.jpg) 57 | 58 | 自动下载新种子(后台观测时,实际使用时只会在下载完成时通知。) 59 | 60 | ![v1.0](http://i.imgur.com/vI4WHLw.jpg) 61 | 62 | ## 开发环境 63 | 64 | - macOS 11.1 65 | - Xcode 12.3 66 | 67 | ## Thanks 68 | 69 | - [AFNetworking](https://github.com/AFNetworking/AFNetworking) 70 | - [Ono](https://github.com/mattt/Ono) 71 | - [AFOnoResponseSerializer](https://github.com/AFNetworking/AFOnoResponseSerializer) 72 | - [DateTools](https://github.com/MatthewYork/DateTools) 73 | - [CocoaPods](https://cocoapods.org/) 74 | - [stackoverflow](http://stackoverflow.com/) 75 | - [raywenderlich](https://www.raywenderlich.com/) 76 | - [Google search](https://www.google.com/) 77 | - [NSHipster](http://nshipster.com/) 78 | - [objc](https://www.objc.io/) 79 | 80 | and others. 81 | 82 | ## License 83 | Under MIT license, see LICENSE file. 84 | 85 | 86 | --------------------------------------------------------------------------------