├── Mounting Yard ├── Art │ ├── menu.png │ ├── settings.png │ ├── spotlight.png │ └── standard_conenction_dialog.png ├── Assets.xcassets │ ├── Contents.json │ ├── menu-icons │ │ ├── Contents.json │ │ ├── icon-afp.imageset │ │ │ ├── icon-afp-16x16.png │ │ │ ├── icon-afp-16x16@2x.png │ │ │ └── Contents.json │ │ ├── icon-ftp.imageset │ │ │ ├── icon-ftp-16x16.png │ │ │ ├── icon-ftp-16x16@2x.png │ │ │ └── Contents.json │ │ ├── icon-smb.imageset │ │ │ ├── icon-smb-16x16.png │ │ │ ├── icon-smb-16x16@2x.png │ │ │ └── Contents.json │ │ ├── icon-vnc.imageset │ │ │ ├── icon-vnc-16x16.png │ │ │ ├── icon-vnc-16x16@2x.png │ │ │ └── Contents.json │ │ └── icon-unknown.imageset │ │ │ ├── icon-unknown-16x16.png │ │ │ ├── icon-unknown-16x16@2x.png │ │ │ └── Contents.json │ ├── Application Icons │ │ ├── Contents.json │ │ ├── AppIcon.appiconset │ │ │ ├── icon_16x16.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_128x128.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_512x512.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256@2x.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512@2x.png │ │ │ └── Contents.json │ │ ├── ArchiveIcon.iconset │ │ │ ├── icon_16x16.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_128x128.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_256x256@2x.png │ │ │ └── icon_512x512@2x.png │ │ └── DocumentIcon.iconset │ │ │ ├── icon_16x16.png │ │ │ ├── icon_32x32.png │ │ │ ├── icon_128x128.png │ │ │ ├── icon_16x16@2x.png │ │ │ ├── icon_256x256.png │ │ │ ├── icon_32x32@2x.png │ │ │ ├── icon_512x512.png │ │ │ ├── icon_128x128@2x.png │ │ │ ├── icon_256x256@2x.png │ │ │ └── icon_512x512@2x.png │ └── MenuIcon.imageset │ │ ├── Icon_16x16.png │ │ ├── Icon_16x16@2x.png │ │ └── Contents.json ├── Resources │ ├── MenuIcons.xcf │ ├── MenuIcons-16x16.xcf │ ├── Credits.rtf │ └── DSFMountingYardSettingsWindowController.xib ├── Mounting_Yard.entitlements ├── Mounting Yard.entitlements ├── DSFImageManager.swift ├── Info.plist ├── AppDelegate.swift ├── DSFMountingYardSettingsWindowController.swift ├── DSFMountingYardStatusMenu.swift ├── DSFMountingYardItem.swift ├── NetFSShare.swift ├── DSFMountingYardController.swift └── Base.lproj │ └── MainMenu.xib ├── Mounting Yard.xcodeproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── xcuserdata │ └── dford.xcuserdatad │ │ └── xcschemes │ │ └── xcschememanagement.plist └── project.pbxproj ├── Mounting PointTests ├── Info.plist └── Mounting_PointTests.swift ├── LICENSE └── README.md /Mounting Yard/Art/menu.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Art/menu.png -------------------------------------------------------------------------------- /Mounting Yard/Art/settings.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Art/settings.png -------------------------------------------------------------------------------- /Mounting Yard/Art/spotlight.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Art/spotlight.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Mounting Yard/Resources/MenuIcons.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Resources/MenuIcons.xcf -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "version" : 1, 4 | "author" : "xcode" 5 | } 6 | } -------------------------------------------------------------------------------- /Mounting Yard/Resources/MenuIcons-16x16.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Resources/MenuIcons-16x16.xcf -------------------------------------------------------------------------------- /Mounting Yard/Art/standard_conenction_dialog.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Art/standard_conenction_dialog.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/MenuIcon.imageset/Icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/MenuIcon.imageset/Icon_16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/MenuIcon.imageset/Icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/MenuIcon.imageset/Icon_16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-afp.imageset/icon-afp-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-afp.imageset/icon-afp-16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-ftp.imageset/icon-ftp-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-ftp.imageset/icon-ftp-16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-smb.imageset/icon-smb-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-smb.imageset/icon-smb-16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-vnc.imageset/icon-vnc-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-vnc.imageset/icon-vnc-16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_32x32.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-afp.imageset/icon-afp-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-afp.imageset/icon-afp-16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-ftp.imageset/icon-ftp-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-ftp.imageset/icon-ftp-16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-smb.imageset/icon-smb-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-smb.imageset/icon-smb-16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-vnc.imageset/icon-vnc-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-vnc.imageset/icon-vnc-16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_128x128.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_256x256.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_512x512.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_32x32.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/AppIcon.appiconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_128x128.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_128x128.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_256x256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_256x256.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_32x32@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_32x32@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_512x512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_512x512.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-unknown.imageset/icon-unknown-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-unknown.imageset/icon-unknown-16x16.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/ArchiveIcon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_128x128@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_128x128@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_256x256@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_256x256@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_512x512@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/Application Icons/DocumentIcon.iconset/icon_512x512@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-unknown.imageset/icon-unknown-16x16@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dagronf/MountingYard/HEAD/Mounting Yard/Assets.xcassets/menu-icons/icon-unknown.imageset/icon-unknown-16x16@2x.png -------------------------------------------------------------------------------- /Mounting Yard/Mounting_Yard.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /Mounting Yard.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /Mounting Yard.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /Mounting Yard/Mounting Yard.entitlements: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | com.apple.security.app-sandbox 6 | 7 | com.apple.security.files.user-selected.read-write 8 | 9 | com.apple.security.network.client 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /Mounting Yard/Resources/Credits.rtf: -------------------------------------------------------------------------------- 1 | {\rtf1\ansi\ansicpg1252\cocoartf1561\cocoasubrtf600 2 | {\fonttbl\f0\fswiss\fcharset0 Helvetica;} 3 | {\colortbl;\red255\green255\blue255;\red0\green0\blue0;} 4 | {\*\expandedcolortbl;;\csgray\c0\c0;} 5 | \paperw11900\paperh16840\margl1440\margr1440\vieww10800\viewh8400\viewkind0 6 | \pard\tx566\tx1133\tx1700\tx2267\tx2834\tx3401\tx3968\tx4535\tx5102\tx5669\tx6236\tx6803\pardirnatural\partightenfactor0 7 | 8 | \f0\fs26 \cf0 \cb2 \ 9 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/MenuIcon.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "Icon_16x16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "Icon_16x16@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-afp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-afp-16x16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-afp-16x16@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-ftp.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-ftp-16x16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-ftp-16x16@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-smb.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-smb-16x16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-smb-16x16@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-vnc.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-vnc-16x16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-vnc-16x16@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/menu-icons/icon-unknown.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "filename" : "icon-unknown-16x16.png", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "filename" : "icon-unknown-16x16@2x.png", 11 | "scale" : "2x" 12 | }, 13 | { 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "version" : 1, 20 | "author" : "xcode" 21 | } 22 | } -------------------------------------------------------------------------------- /Mounting Yard.xcodeproj/xcuserdata/dford.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | Mounting Point.xcscheme 8 | 9 | orderHint 10 | 0 11 | 12 | Mounting Yard Spotlight Importer.xcscheme 13 | 14 | orderHint 15 | 1 16 | 17 | Mounting Yard.xcscheme 18 | 19 | orderHint 20 | 0 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /Mounting PointTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Darren Ford 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. -------------------------------------------------------------------------------- /Mounting PointTests/Mounting_PointTests.swift: -------------------------------------------------------------------------------- 1 | // 2 | // Mounting_PointTests.swift 3 | // Mounting PointTests 4 | // 5 | // Created by Darren Ford on 31/7/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | import XCTest 10 | @testable import Mounting_Point 11 | 12 | class Mounting_PointTests: XCTestCase { 13 | 14 | override func setUp() { 15 | super.setUp() 16 | // Put setup code here. This method is called before the invocation of each test method in the class. 17 | } 18 | 19 | override func tearDown() { 20 | // Put teardown code here. This method is called after the invocation of each test method in the class. 21 | super.tearDown() 22 | } 23 | 24 | func testExample() { 25 | // This is an example of a functional test case. 26 | // Use XCTAssert and related functions to verify your tests produce the correct results. 27 | } 28 | 29 | func testPerformanceExample() { 30 | // This is an example of a performance test case. 31 | self.measure { 32 | // Put the code you want to measure the time of here. 33 | } 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Mounting Yard/Assets.xcassets/Application Icons/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 | } -------------------------------------------------------------------------------- /Mounting Yard/DSFImageManager.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFImageManager.swift 3 | // Mounting Yard 4 | // 5 | // Created by Darren Ford on 4/8/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2018 Darren Ford 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import Cocoa 32 | 33 | class DSFImageManager 34 | { 35 | static func afpImage() -> NSImage 36 | { 37 | return NSImage(named: NSImage.Name(rawValue: "icon-afp"))! 38 | } 39 | 40 | static func ftpImage() -> NSImage 41 | { 42 | return NSImage(named: NSImage.Name(rawValue: "icon-ftp"))! 43 | } 44 | 45 | static func smbImage() -> NSImage 46 | { 47 | return NSImage(named: NSImage.Name(rawValue: "icon-smb"))! 48 | } 49 | 50 | static func vncImage() -> NSImage 51 | { 52 | return NSImage(named: NSImage.Name(rawValue: "icon-vnc"))! 53 | } 54 | 55 | static func unknownImage() -> NSImage 56 | { 57 | return NSImage(named: NSImage.Name(rawValue: "icon-unknown"))! 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Mounting Yard/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleDocumentTypes 8 | 9 | 10 | CFBundleTypeExtensions 11 | 12 | mountingYard 13 | 14 | CFBundleTypeIconFile 15 | DocumentIcon 16 | CFBundleTypeName 17 | Mounting Yard Document 18 | CFBundleTypeRole 19 | Editor 20 | NSDocumentClass 21 | 22 | 23 | 24 | CFBundleTypeExtensions 25 | 26 | mountingYardArchive 27 | 28 | CFBundleTypeIconFile 29 | ArchiveIcon 30 | CFBundleTypeName 31 | Mounting Yard Archive 32 | CFBundleTypeRole 33 | Editor 34 | 35 | 36 | CFBundleExecutable 37 | $(EXECUTABLE_NAME) 38 | CFBundleIconFile 39 | 40 | CFBundleIdentifier 41 | $(PRODUCT_BUNDLE_IDENTIFIER) 42 | CFBundleInfoDictionaryVersion 43 | 6.0 44 | CFBundleName 45 | $(PRODUCT_NAME) 46 | CFBundlePackageType 47 | APPL 48 | CFBundleShortVersionString 49 | 1.2 50 | CFBundleVersion 51 | 1 52 | LSApplicationCategoryType 53 | public.app-category.utilities 54 | LSMinimumSystemVersion 55 | $(MACOSX_DEPLOYMENT_TARGET) 56 | LSUIElement 57 | 58 | NSHumanReadableCopyright 59 | Copyright © 2018 Darren Ford. All rights reserved. 60 | NSMainNibFile 61 | MainMenu 62 | NSPrincipalClass 63 | NSApplication 64 | 65 | 66 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Mounting Yard 2 | 3 | A simple Mac application that allows you to see and connect to your favourite mount points in the menu bar. 4 | 5 | The functionality provided by the finder (using the 'Connect to Server…' menu) is great, however means that you have to have the finder active in order to quickly connect. Additionally the finder UI does not support friendly naming, thus you have to understand the difference between (eg) `smb://144.33.1.1` and `smb://144.33.1.2` in your brainz if your connection points are not mapped within a DNS 6 | 7 | Supported mount point types :- 8 | 9 | * `afp://` 10 | * `smb://` 11 | * `cifs://` 12 | * `ftp://` 13 | 14 | If a scheme is provided that the application doesn't support, it passes it on to the Finder to attempt to locate a program to support the URL. 15 | 16 | For example :- 17 | 18 | * `vnc://` (opens Screen Sharing) 19 | * `rdp://` (if you have Microsoft Remote Desktop installed, it will attempt to open it) 20 | 21 | 22 | ## Interface 23 | The main interface lives in the menu bar, with a menu to allow your mounting points to be quickly accessed. 24 | 25 | ![./Mounting%20Yard/Art/menu.png](./Mounting%20Yard/Art/menu.png) 26 | 27 | ## Settings 28 | 29 | The stored mounting points are accessed via the `Settings…` menu from the main Mounting Point menu. 30 | 31 | ![./Mounting%20Yard/Art/settings.png](./Mounting%20Yard/Art/settings.png) 32 | 33 | Note that this version doesn't support storing passwords. If you want automatic connection to a mount point use the standard server connection dialog that appears when you connect to a server to remember the password in the keychain. 34 | 35 | ![./Mounting%20Yard/Art/standard_conenction_dialog.png](./Mounting%20Yard/Art/standard_conenction_dialog.png) 36 | 37 | ## Spotlight 38 | 39 | Mounting Yard stores the settings for a server as a file on your hard drive, which is indexed by Spotlight. Thus, you can use spotlight to quickly access a mount point (even if its not currently connected) with a simple spotlight search 40 | 41 | Due to sandbox restrictions, the files are stored in 42 | 43 | `/Users//Library/Containers/com.darrenford.Mounting-Yard/Data/Documents/` 44 | 45 | which allows for both spotlight indexing and time machine backups 46 | 47 | ![./Mounting%20Yard/Art/spotlight.png](./Mounting%20Yard/Art/spotlight.png) 48 | -------------------------------------------------------------------------------- /Mounting Yard/AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // Mounting Point 4 | // 5 | // Created by Darren Ford on 31/7/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2018 Darren Ford 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import Cocoa 32 | 33 | @NSApplicationMain 34 | class AppDelegate: NSObject, NSApplicationDelegate 35 | { 36 | @IBOutlet var window: NSWindow! 37 | 38 | lazy var mountingYard: DSFMountingYardController = 39 | { 40 | let yard = DSFMountingYardController() 41 | yard.load() 42 | return yard 43 | }() 44 | 45 | lazy var statusManager: DSFMountingYardStatusMenu = 46 | { 47 | let manager = DSFMountingYardStatusMenu() 48 | manager.configure(controller: self.mountingYard) 49 | return manager 50 | }() 51 | 52 | lazy var settingsWindow: DSFMountingYardSettingsWindowController = 53 | { 54 | let wc = DSFMountingYardSettingsWindowController(windowNibName: NSNib.Name(rawValue: "DSFMountingYardSettingsWindowController")) 55 | wc.mountingYard = mountingYard 56 | return wc 57 | }() 58 | 59 | private func initialize() 60 | { 61 | // Kinda dumb, simple tho! 62 | _ = self.statusManager 63 | } 64 | 65 | func application(_: NSApplication, openFile filename: String) -> Bool 66 | { 67 | self.initialize() 68 | return self.mountingYard.openFile(filename: filename) 69 | } 70 | 71 | func applicationDidFinishLaunching(_: Notification) 72 | { 73 | self.initialize() 74 | 75 | // If there's no entries, display the settings window by default 76 | if self.mountingYard.items.count == 0 77 | { 78 | self.showSettings() 79 | } 80 | } 81 | 82 | func applicationWillTerminate(_: Notification) 83 | { 84 | // Sync any changes 85 | _ = self.mountingYard.save() 86 | } 87 | 88 | func showSettings() 89 | { 90 | NSApp.activate(ignoringOtherApps: true) 91 | self.settingsWindow.window?.makeKeyAndOrderFront(self) 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Mounting Yard/DSFMountingYardSettingsWindowController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFMountingYardSettingsWindowController.swift 3 | // Mounting Yard 4 | // 5 | // Created by Darren Ford on 3/8/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2018 Darren Ford 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import Cocoa 32 | 33 | class DSFMountingYardSettingsWindowController: NSWindowController, NSWindowDelegate, NSTextFieldDelegate 34 | { 35 | @objc var mountingYard: DSFMountingYardController? 36 | 37 | @IBOutlet var nameField: NSTextField! 38 | @IBOutlet var addressField: NSTextField! 39 | 40 | @IBOutlet var itemsArrayController: NSArrayController! 41 | 42 | override func windowDidLoad() 43 | { 44 | super.windowDidLoad() 45 | 46 | self.window!.isMovableByWindowBackground = true 47 | 48 | // Implement this method to handle any initialization after your window controller's window has been loaded from its nib file. 49 | } 50 | 51 | @objc func yardController() -> DSFMountingYardController 52 | { 53 | return self.mountingYard! 54 | } 55 | 56 | func windowDidResignKey(_: Notification) 57 | { 58 | // If the settings window disappears, sync the updates to disk 59 | self.window?.makeFirstResponder(nil) 60 | _ = self.yardController().save() 61 | } 62 | 63 | @IBAction func addItem(_ sender: Any) 64 | { 65 | self.itemsArrayController.add(sender) 66 | 67 | if self.itemsArrayController.selectedObjects.first != nil 68 | { 69 | self.nameField.selectText(nil) 70 | } 71 | } 72 | 73 | override func controlTextDidChange(_ obj: Notification) 74 | { 75 | if let control = obj.object as? NSTextField, 76 | control === self.addressField 77 | { 78 | // For the address field, validate every keypress 79 | if let item = self.itemsArrayController.selectedObjects.first as? DSFMountingYardItem 80 | { 81 | item.address = control.stringValue 82 | } 83 | } 84 | } 85 | 86 | private func updateNameIfDuplicateFound(_ item: DSFMountingYardItem, updatedName: String) 87 | { 88 | for otherItem in self.itemsArrayController.arrangedObjects as! [DSFMountingYardItem] 89 | { 90 | if otherItem !== item, 91 | otherItem.name == updatedName 92 | { 93 | var newName = updatedName 94 | newName.append(" ") 95 | newName.append(NSLocalizedString("Copy", comment: "The string that gets appended to the name if its a duplicate")) 96 | if let item = self.itemsArrayController.selectedObjects.first as? DSFMountingYardItem 97 | { 98 | item.name = newName 99 | } 100 | break 101 | } 102 | } 103 | } 104 | 105 | override func controlTextDidEndEditing(_ obj: Notification) 106 | { 107 | // If the name field completes, update it to make it unique 108 | 109 | // This appears to get called AFTER the array controller has updated the content. 110 | 111 | if let control = obj.object as? NSTextField, 112 | let item = self.itemsArrayController.selectedObjects.first as? DSFMountingYardItem, 113 | control === self.nameField 114 | { 115 | if item.name.count == 0 116 | { 117 | item.name = "empty" 118 | } 119 | self.updateNameIfDuplicateFound(item, updatedName: control.stringValue) 120 | } 121 | } 122 | 123 | @IBAction func testConnection(_: Any) 124 | { 125 | if let item = itemsArrayController.selectedObjects.first as? DSFMountingYardItem 126 | { 127 | _ = self.yardController().mount(item: item) 128 | } 129 | } 130 | 131 | @IBAction func actionButton(_ sender: NSButton) 132 | { 133 | NSMenu.popUpContextMenu(sender.menu!, 134 | with: NSApp.currentEvent!, 135 | for: sender) 136 | } 137 | 138 | @IBAction func share(_: Any) 139 | { 140 | _ = self.yardController().shareItems() 141 | } 142 | 143 | @IBAction func importData(_: Any) 144 | { 145 | _ = self.yardController().importItems() 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /Mounting Yard/DSFMountingYardStatusMenu.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFMountingYardStatusMenu.swift 3 | // Mounting Point 4 | // 5 | // Created by Darren Ford on 31/7/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2018 Darren Ford 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import Cocoa 32 | 33 | class DSFMountingYardStatusMenu: NSObject, NSMenuDelegate 34 | { 35 | let statusBar = NSStatusBar.system.statusItem(withLength: 30) 36 | let statusMenu = NSMenu() 37 | 38 | var controller: DSFMountingYardController? 39 | 40 | private lazy var settingsMenu: NSMenuItem = 41 | { 42 | let settingsMenu = NSMenuItem( 43 | title: NSLocalizedString("Settings…", comment: "Settings menu entry"), 44 | action: #selector(self.showSettings(_:)), 45 | keyEquivalent: "" 46 | ) 47 | settingsMenu.target = self 48 | settingsMenu.isEnabled = true 49 | return settingsMenu 50 | }() 51 | 52 | private lazy var aboutMenu: NSMenuItem = 53 | { 54 | let aboutMenu = NSMenuItem( 55 | title: NSLocalizedString("About Mounting Yard…", comment: "About menu entry"), 56 | action: #selector(self.showAbout(_:)), 57 | keyEquivalent: "" 58 | ) 59 | aboutMenu.target = self 60 | aboutMenu.isEnabled = true 61 | return aboutMenu 62 | }() 63 | 64 | private lazy var quitMenu: NSMenuItem = 65 | { 66 | let quitMenu = NSMenuItem( 67 | title: NSLocalizedString("Quit", comment: "Quit menu entry"), 68 | action: #selector(NSApplication.terminate(_:)), 69 | keyEquivalent: "" 70 | ) 71 | quitMenu.target = NSApplication.shared 72 | quitMenu.isEnabled = true 73 | return quitMenu 74 | }() 75 | 76 | func configure(controller: DSFMountingYardController) 77 | { 78 | self.controller = controller 79 | 80 | let menuIcon = NSImage(named: NSImage.Name(rawValue: "MenuIcon")) 81 | menuIcon!.isTemplate = true 82 | statusBar.image = menuIcon 83 | statusBar.menu = statusMenu 84 | statusMenu.delegate = self 85 | } 86 | 87 | func menuWillOpen(_ menu: NSMenu) 88 | { 89 | menu.removeAllItems() 90 | self.configureMenu() 91 | } 92 | 93 | private func resize(image: NSImage, w: Int, h: Int) -> NSImage 94 | { 95 | let destSize = NSMakeSize(CGFloat(w), CGFloat(h)) 96 | let newImage = NSImage(size: destSize) 97 | newImage.lockFocus() 98 | image.draw( 99 | in: NSMakeRect(0, 0, destSize.width, destSize.height), 100 | from: NSMakeRect(0, 0, image.size.width, image.size.height), 101 | operation: .sourceOver, 102 | fraction: CGFloat(1) 103 | ) 104 | newImage.unlockFocus() 105 | newImage.size = destSize 106 | return NSImage(data: newImage.tiffRepresentation!)! 107 | } 108 | 109 | private func configureMenu() 110 | { 111 | // Sort the items in the correct order 112 | var items = controller!.items 113 | items.sort { $0.name.lowercased() < $1.name.lowercased() } 114 | 115 | for item in items 116 | { 117 | let itemMenu = NSMenuItem( 118 | title: item.name, 119 | action: #selector(self.openItem(_:)), 120 | keyEquivalent: "" 121 | ) 122 | itemMenu.representedObject = item 123 | itemMenu.target = self 124 | itemMenu.isEnabled = true 125 | itemMenu.image = item.icon 126 | 127 | self.statusMenu.addItem(itemMenu) 128 | } 129 | 130 | self.statusMenu.addItem(NSMenuItem.separator()) 131 | self.statusMenu.addItem(self.settingsMenu) 132 | self.statusMenu.addItem(self.aboutMenu) 133 | self.statusMenu.addItem(NSMenuItem.separator()) 134 | self.statusMenu.addItem(self.quitMenu) 135 | } 136 | 137 | @objc func openItem(_ sender: NSMenuItem) 138 | { 139 | self.controller!.mount(item: sender.representedObject as! DSFMountingYardItem) 140 | } 141 | 142 | @objc func showAbout(_: NSMenuItem) 143 | { 144 | NSApp.orderFrontStandardAboutPanel(self) 145 | } 146 | 147 | @objc func showSettings(_: NSMenuItem) 148 | { 149 | if let delegate = NSApp.delegate as? AppDelegate 150 | { 151 | delegate.showSettings() 152 | } 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /Mounting Yard/DSFMountingYardItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFMountingYardItem.swift 3 | // Mounting Yard 4 | // 5 | // Created by Darren Ford on 31/7/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2018 Darren Ford 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import Cocoa 32 | 33 | @objc(DSFMountingYardItem) 34 | public class DSFMountingYardItem: NSObject 35 | { 36 | var url: URL? 37 | var modified: Bool = false 38 | 39 | static var detector: NSDataDetector = { 40 | let types: NSTextCheckingResult.CheckingType = [.link] 41 | let detector = try? NSDataDetector(types: types.rawValue) 42 | return detector! 43 | }() 44 | 45 | @objc dynamic var urlValid: Bool 46 | { 47 | if DSFMountingYardItem.detector.numberOfMatches( 48 | in: self.address, 49 | options: NSRegularExpression.MatchingOptions(rawValue: 0), 50 | range: NSMakeRange(0, self.address.count) 51 | ) > 0 52 | { 53 | return true 54 | } 55 | return false 56 | } 57 | 58 | @objc dynamic var name: String = "" 59 | { 60 | didSet 61 | { 62 | // File url is now out of sync. Clear it 63 | self.url = nil 64 | self.modified = true 65 | } 66 | } 67 | 68 | @objc dynamic var icon: NSImage = DSFImageManager.unknownImage() 69 | 70 | @objc dynamic var address: String = "" 71 | { 72 | didSet 73 | { 74 | self.willChangeValue(forKey: "urlValid") 75 | self.modified = true 76 | self.didChangeValue(forKey: "urlValid") 77 | 78 | self.syncIcon() 79 | } 80 | } 81 | 82 | @objc dynamic var username: String = "" 83 | { 84 | didSet 85 | { 86 | self.modified = true 87 | } 88 | } 89 | 90 | @objc dynamic var guest: Bool = false 91 | { 92 | didSet 93 | { 94 | self.modified = true 95 | } 96 | } 97 | 98 | @objc dynamic var mountedPoint: URL? 99 | { 100 | didSet 101 | { 102 | self.modified = true 103 | } 104 | } 105 | 106 | var connecting: Bool = false 107 | 108 | func validatedMountPoint() -> URL? 109 | { 110 | var b: ObjCBool = false 111 | if let mountPoint = self.mountedPoint 112 | { 113 | if FileManager.default.fileExists(atPath: mountPoint.path, isDirectory: &b) 114 | { 115 | return mountPoint 116 | } 117 | else 118 | { 119 | // The mount point doesn't exist anymore -- the user might have ejected it! 120 | self.mountedPoint = nil 121 | } 122 | } 123 | return nil 124 | } 125 | 126 | public override init() 127 | { 128 | super.init() 129 | 130 | // Dummy values for new object 131 | self.name = "new server" 132 | self.address = "scheme://localhost" 133 | self.syncIcon() 134 | } 135 | 136 | public init(url: URL, address: String, username: String, guest: Bool = false) 137 | { 138 | super.init() 139 | 140 | self.url = url 141 | self.name = (url.lastPathComponent as NSString).deletingPathExtension 142 | self.address = address 143 | self.username = username 144 | self.guest = guest 145 | 146 | self.syncIcon() 147 | } 148 | 149 | func syncIcon() 150 | { 151 | self.willChangeValue(forKey: "icon") 152 | self.modified = true 153 | 154 | let url = URL(string: address) 155 | switch url?.scheme 156 | { 157 | case "afp": 158 | self.icon = DSFImageManager.afpImage() 159 | case "ftp": 160 | self.icon = DSFImageManager.ftpImage() 161 | case "smb": 162 | self.icon = DSFImageManager.smbImage() 163 | case "vnc": 164 | self.icon = DSFImageManager.vncImage() 165 | default: 166 | self.icon = DSFImageManager.unknownImage() 167 | } 168 | 169 | self.didChangeValue(forKey: "icon") 170 | } 171 | 172 | func toDictionary() -> [String: Any] 173 | { 174 | var dict = ["address": self.address, "username": self.username] 175 | if self.guest 176 | { 177 | dict["guest"] = "yes" 178 | } 179 | return dict 180 | } 181 | 182 | static func fromDictionary(fileURL: URL, json: [String: Any]) -> DSFMountingYardItem? 183 | { 184 | let item = DSFMountingYardItem( 185 | url: fileURL, 186 | address: json["address"] as! String, 187 | username: json["username"] as! String, 188 | guest: json["guest"] != nil 189 | ) 190 | return item 191 | } 192 | 193 | static func fromDictionary(json: [String: Any]) -> DSFMountingYardItem? 194 | { 195 | let item = DSFMountingYardItem() 196 | item.address = json["address"] as! String 197 | item.username = json["username"] as! String 198 | item.guest = json["guest"] != nil 199 | return item 200 | } 201 | } 202 | -------------------------------------------------------------------------------- /Mounting Yard/NetFSShare.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NetFSShare.swift 3 | // Mounting Yard 4 | // 5 | // Created by Darren Ford on 31/7/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | // https://gist.github.com/mosen/2ddf85824fbb5564aef527b60beb4669 9 | // 10 | 11 | import Foundation 12 | import NetFS 13 | 14 | enum ShareMountError: Error { 15 | case InvalidURL 16 | case MountpointInaccessible 17 | case InvalidMountOptions 18 | } 19 | 20 | enum MountOption { 21 | case NoBrowse 22 | case ReadOnly 23 | case AllowSubMounts 24 | case SoftMount 25 | case MountAtMountDirectory 26 | 27 | case Guest 28 | case AllowLoopback 29 | case NoAuthDialog 30 | case AllowAuthDialog 31 | case ForceAuthDialog 32 | } 33 | 34 | typealias NetFSMountCallback = (Int32, UnsafeMutableRawPointer?, CFArray?) -> Void 35 | typealias MountCallbackHandler = (Int32, URL?, [String]?) -> Void; 36 | 37 | protocol ShareDelegate { 38 | func shareWillMount(url: URL) -> Void 39 | func shareDidMount(url: URL, at paths: [String]?) -> Void 40 | func shareMountingDidFail(for url: URL, withError: Int32) -> Void 41 | } 42 | 43 | fileprivate func processOptionsForNetFS(options: [MountOption]) throws -> (NSMutableDictionary, NSMutableDictionary) { 44 | let openOptions: NSMutableDictionary = NSMutableDictionary() 45 | let mountOptions: NSMutableDictionary = NSMutableDictionary() 46 | 47 | for opt in options { 48 | switch opt { 49 | 50 | // mount_options 51 | case .NoBrowse: 52 | if let existingValue = mountOptions.value(forKey: kNetFSMountFlagsKey) { 53 | mountOptions[kNetFSMountFlagsKey] = existingValue as! Int32 | MNT_DONTBROWSE 54 | } else { 55 | mountOptions[kNetFSMountFlagsKey] = MNT_DONTBROWSE 56 | } 57 | case .ReadOnly: 58 | if let existingValue = mountOptions.value(forKey: kNetFSMountFlagsKey) { 59 | mountOptions[kNetFSMountFlagsKey] = existingValue as! Int32 | MNT_RDONLY 60 | } else { 61 | mountOptions[kNetFSMountFlagsKey] = MNT_RDONLY 62 | } 63 | case .AllowSubMounts: 64 | mountOptions[kNetFSAllowSubMountsKey] = true 65 | case .SoftMount: 66 | mountOptions[kNetFSSoftMountKey] = true 67 | case .MountAtMountDirectory: 68 | mountOptions[kNetFSMountAtMountDirKey] = true 69 | 70 | // open_options 71 | case .Guest: 72 | openOptions[kNetFSUseGuestKey] = true 73 | case .AllowLoopback: 74 | openOptions[kNetFSAllowLoopbackKey] = true 75 | case .NoAuthDialog: 76 | openOptions[kNAUIOptionKey] = kNAUIOptionNoUI 77 | case .AllowAuthDialog: 78 | openOptions[kNAUIOptionKey] = kNAUIOptionAllowUI 79 | case .ForceAuthDialog: 80 | openOptions[kNAUIOptionKey] = kNAUIOptionForceUI 81 | } 82 | } 83 | 84 | return (openOptions, mountOptions) 85 | } 86 | 87 | 88 | class Share { 89 | let url: URL 90 | var mountPoint: String = "/Volumes" 91 | var username: String? 92 | var password: String? 93 | fileprivate var asyncRequestId: AsyncRequestID? 94 | public var delegate: ShareDelegate? 95 | 96 | init(_ url: URL) { 97 | self.url = url 98 | } 99 | 100 | init(_ urlString: String) throws { 101 | guard let url = URL(string: urlString) else { 102 | throw ShareMountError.InvalidURL 103 | } 104 | 105 | self.url = url 106 | } 107 | 108 | public func cancelMounting() { 109 | NetFSMountURLCancel(self.asyncRequestId) 110 | } 111 | 112 | static func cancelMounting(id requestId: AsyncRequestID) { 113 | NetFSMountURLCancel(requestId) 114 | } 115 | 116 | public func mount() throws { 117 | let mountDirectoryURL = URL(fileURLWithPath: self.mountPoint) 118 | let operationQueue = OperationQueue.main 119 | 120 | let mountReportBlock: NetFSMountCallback = { 121 | status, asyncRequestId, mountedDirs in 122 | 123 | let mountedDirectories = mountedDirs as! [String]? ?? nil 124 | 125 | if (status != 0) { 126 | self.delegate?.shareMountingDidFail(for: self.url, withError: status) 127 | } else { 128 | self.delegate?.shareDidMount(url: self.url, at: mountedDirectories) 129 | } 130 | } 131 | 132 | NetFSMountURLAsync(url as CFURL, 133 | mountDirectoryURL as CFURL, 134 | username as CFString?, 135 | password as CFString?, 136 | nil, 137 | nil, 138 | &self.asyncRequestId, 139 | operationQueue.underlyingQueue, 140 | mountReportBlock) 141 | self.delegate?.shareWillMount(url: url) 142 | } 143 | 144 | public func mount(options: [MountOption]?, callbackHandler: @escaping MountCallbackHandler) throws -> AsyncRequestID? { 145 | let mountDirectoryURL = URL(fileURLWithPath: self.mountPoint) 146 | let operationQueue = OperationQueue.main 147 | 148 | let mountReportBlock: NetFSMountCallback = { 149 | (status, asyncRequestId, mountedDirs) in 150 | callbackHandler(status, self.url, mountedDirs as? [String]) 151 | } 152 | 153 | var openOptions: NSMutableDictionary 154 | var mountOptions: NSMutableDictionary 155 | 156 | if options != nil { 157 | (openOptions, mountOptions) = try processOptionsForNetFS(options: options!) 158 | } else { 159 | openOptions = NSMutableDictionary() 160 | mountOptions = NSMutableDictionary() 161 | } 162 | 163 | NetFSMountURLAsync(url as CFURL, 164 | mountDirectoryURL as CFURL, 165 | username as CFString?, 166 | password as CFString?, 167 | openOptions as CFMutableDictionary, 168 | mountOptions as CFMutableDictionary, 169 | &self.asyncRequestId, 170 | operationQueue.underlyingQueue, 171 | mountReportBlock) 172 | self.delegate?.shareWillMount(url: url) 173 | return self.asyncRequestId 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /Mounting Yard/DSFMountingYardController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // DSFMountingYardController.swift 3 | // Mounting Yard 4 | // 5 | // Created by Darren Ford on 31/7/18. 6 | // Copyright © 2018 Darren Ford. All rights reserved. 7 | // 8 | 9 | // MIT License 10 | // 11 | // Copyright (c) 2018 Darren Ford 12 | // 13 | // Permission is hereby granted, free of charge, to any person obtaining a copy 14 | // of this software and associated documentation files (the "Software"), to deal 15 | // in the Software without restriction, including without limitation the rights 16 | // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 17 | // copies of the Software, and to permit persons to whom the Software is 18 | // furnished to do so, subject to the following conditions: 19 | // 20 | // The above copyright notice and this permission notice shall be included in all 21 | // copies or substantial portions of the Software. 22 | // 23 | // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 24 | // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 25 | // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 26 | // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 27 | // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 28 | // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 29 | // SOFTWARE. 30 | 31 | import Cocoa 32 | import NetFS 33 | 34 | @objc public class DSFMountingYardController: NSObject, NSUserNotificationCenterDelegate 35 | { 36 | lazy var filesLocation: URL = { 37 | var appSupport = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask).first 38 | appSupport!.appendPathComponent("Mounting Yard") 39 | try? FileManager.default.createDirectory(at: appSupport!, withIntermediateDirectories: false, attributes: nil) 40 | return appSupport! 41 | }() 42 | 43 | lazy var mountLocation: URL = { 44 | var userLoc = filesLocation 45 | userLoc.appendPathComponent("mounts") 46 | try? FileManager.default.createDirectory(at: userLoc, withIntermediateDirectories: false, attributes: nil) 47 | return userLoc 48 | }() 49 | 50 | let knownSchemes = Set(["smb", "afp", "cifs", "ftp"]) 51 | 52 | // The urls which we successfully loaded from 53 | var managedUrls: [URL] = [] 54 | 55 | @objc public dynamic var items: [DSFMountingYardItem] = [] 56 | 57 | var activeRequests: [AsyncRequestID: DSFMountingYardItem] = [:] 58 | 59 | } 60 | 61 | // MARK: Loading and saving 62 | 63 | extension DSFMountingYardController 64 | { 65 | func load() 66 | { 67 | NSUserNotificationCenter.default.delegate = self 68 | do 69 | { 70 | if let fileURLs = try? FileManager.default.contentsOfDirectory( 71 | at: self.filesLocation, 72 | includingPropertiesForKeys: nil, 73 | options: [.skipsHiddenFiles, .skipsSubdirectoryDescendants] 74 | ) 75 | { 76 | let filtered = fileURLs.filter { $0.pathExtension == "mountingYard" } 77 | for url in filtered 78 | { 79 | if let data = try? Data(contentsOf: url) 80 | { 81 | if let rawData = try? JSONSerialization.jsonObject(with: data, options: []), 82 | let json = rawData as? [String: Any] 83 | { 84 | if let item = DSFMountingYardItem.fromDictionary(fileURL: url, json: json) 85 | { 86 | self.managedUrls.append(url) 87 | self.items.append(item) 88 | } 89 | } 90 | } 91 | } 92 | } 93 | } 94 | } 95 | 96 | func openFile(filename: String) -> Bool 97 | { 98 | let url = URL(fileURLWithPath: filename) 99 | if url.pathExtension == "mountingYard" 100 | { 101 | let items = self.items.filter { $0.url == url } 102 | if items.count == 1 103 | { 104 | return self.mount(item: items.first!) 105 | } 106 | } 107 | return false 108 | } 109 | 110 | func save() -> Bool 111 | { 112 | // Make sure that the urls are synced 113 | for item in self.items 114 | { 115 | if item.url == nil 116 | { 117 | item.url = self.filesLocation.appendingPathComponent(item.name).appendingPathExtension("mountingYard") 118 | item.modified = true 119 | } 120 | } 121 | 122 | // Loop through the 'managed' urls (ie. the ones we loaded from) 123 | // and check to see if there's an item with the same url. 124 | // If there's not a matching url, then it has been deleted or renamed 125 | // We can remove it 126 | do 127 | { 128 | for url in self.managedUrls 129 | { 130 | let matching = self.items.filter { $0.url == url } 131 | if matching.count == 0 132 | { 133 | // This has been deleted or renamed. Remove the file 134 | try FileManager.default.removeItem(at: url) 135 | } 136 | } 137 | } 138 | catch 139 | { 140 | // 141 | } 142 | 143 | // Only sync the ones marked as modified 144 | let modifiedItems = self.items.filter { $0.modified == true } 145 | for item in modifiedItems 146 | { 147 | let dict = item.toDictionary() 148 | do 149 | { 150 | let data = try JSONSerialization.data(withJSONObject: dict, options: [.prettyPrinted]) 151 | try data.write(to: item.url!) 152 | try FileManager.default.setAttributes( 153 | [FileAttributeKey.extensionHidden: NSNumber(value: true)], 154 | ofItemAtPath: item.url!.path 155 | ) 156 | } 157 | catch 158 | { 159 | return false 160 | } 161 | 162 | item.modified = false 163 | } 164 | self.managedUrls = self.items.map { $0.url! } 165 | 166 | return true 167 | } 168 | 169 | func shareItems() -> Bool 170 | { 171 | let mySave = NSSavePanel() 172 | mySave.allowedFileTypes = ["mountingYardArchive"] 173 | 174 | mySave.begin { (result) -> Void in 175 | if result == NSApplication.ModalResponse.OK 176 | { 177 | _ = self.shareItems(to: mySave.url!) 178 | } 179 | } 180 | return true 181 | } 182 | 183 | private func shareItems(to url: URL) -> Bool 184 | { 185 | var exportData: [String: Any] = [:] 186 | for item in self.items 187 | { 188 | exportData[item.name] = item.toDictionary() 189 | } 190 | let data = try? JSONSerialization.data(withJSONObject: exportData, options: [.prettyPrinted]) 191 | guard let outputJson = data else 192 | { 193 | return false 194 | } 195 | 196 | do 197 | { 198 | try outputJson.write(to: url) 199 | 200 | try FileManager.default.setAttributes( 201 | [FileAttributeKey.extensionHidden: NSNumber(value: true)], 202 | ofItemAtPath: url.path) 203 | 204 | return true 205 | } 206 | catch 207 | { 208 | return false 209 | } 210 | } 211 | 212 | func importItems() -> Bool 213 | { 214 | let myOpen = NSOpenPanel() 215 | myOpen.allowedFileTypes = ["mountingYardArchive"] 216 | 217 | myOpen.begin { (result) -> Void in 218 | if result == NSApplication.ModalResponse.OK 219 | { 220 | _ = self.importItems(from: myOpen.url!) 221 | } 222 | } 223 | return true 224 | } 225 | 226 | private func importItems(from url: URL) -> Bool 227 | { 228 | if let fileData = try? Data(contentsOf: url), 229 | let jsonData = try? JSONSerialization.jsonObject(with: fileData, options: []) as? [String: Any] 230 | { 231 | for readItem in jsonData! 232 | { 233 | if let itemData = readItem.value as? [String: Any], 234 | let item = DSFMountingYardItem.fromDictionary(json: itemData) 235 | { 236 | let itemName = readItem.key 237 | let matchingItems = self.items.filter { $0.name == itemName } 238 | if matchingItems.count == 0 239 | { 240 | item.name = itemName 241 | item.modified = true 242 | self.items.append(item) 243 | } 244 | } 245 | } 246 | } 247 | return true 248 | } 249 | } 250 | 251 | // MARK: Mounting items 252 | 253 | extension DSFMountingYardController 254 | { 255 | func mount(item: DSFMountingYardItem) -> Bool 256 | { 257 | if let mountPoint = item.validatedMountPoint() 258 | { 259 | // Already mounted! Just show it 260 | NSWorkspace.shared.activateFileViewerSelecting([mountPoint]) 261 | return true 262 | } 263 | 264 | if self.activeRequests.filter( { $0.value == item }).count != 0 265 | { 266 | // Already in the process of making the connection 267 | return true 268 | } 269 | 270 | guard let url = URL(string: item.address), 271 | let scheme = url.scheme, 272 | !scheme.isEmpty else 273 | { 274 | // If we cant parse the URL, or the scheme is missing 275 | return false 276 | } 277 | 278 | if !self.knownSchemes.contains(scheme) 279 | { 280 | // If we don't know what the scheme is, just let the finder handle it 281 | return self.performMountUsingFinder(item: item) 282 | } 283 | 284 | return self.performMount(item: item, serverPath: url) 285 | } 286 | 287 | private func performMount(item: DSFMountingYardItem, serverPath: URL) -> Bool 288 | { 289 | var requestID: AsyncRequestID? 290 | let queue = DispatchQueue.main 291 | 292 | item.connecting = true 293 | 294 | var options: [String: Any]? 295 | if item.guest 296 | { 297 | options = [:] 298 | options![kNetFSUseGuestKey] = true 299 | } 300 | 301 | let result = NetFSMountURLAsync( 302 | serverPath as CFURL, self.mountLocation as CFURL, 303 | nil, nil, 304 | (options != nil) ? (options as! CFMutableDictionary) : nil, 305 | nil, 306 | &requestID, 307 | queue, 308 | self.performMountAsyncCallback()) 309 | 310 | if result != 0 311 | { 312 | print("result: \(result)") 313 | return false 314 | } 315 | else 316 | { 317 | self.activeRequests[requestID!] = item 318 | } 319 | return true 320 | } 321 | 322 | private func performMountAsyncCallback() -> NetFSMountURLBlock 323 | { 324 | return { [weak self] (stat, requestId, mountpoints) in 325 | 326 | print("msg: \(stat) mountpoint: \(String(describing: mountpoints))") 327 | 328 | guard let blockSelf = self else 329 | { 330 | return 331 | } 332 | 333 | guard let item = blockSelf.activeRequests[requestId!] else 334 | { 335 | return 336 | } 337 | blockSelf.activeRequests.removeValue(forKey: requestId!) 338 | 339 | if (stat == 0) 340 | { 341 | item.connecting = false 342 | let pos = mountpoints as! [String] 343 | let urls = pos.map { URL(fileURLWithPath: $0) } 344 | 345 | item.mountedPoint = urls.first! 346 | blockSelf.showConnectionNotification(for: item) 347 | 348 | NSWorkspace.shared.activateFileViewerSelecting(urls) 349 | } 350 | } 351 | } 352 | 353 | private func performMountUsingFinder(item: DSFMountingYardItem) -> Bool 354 | { 355 | guard let url = URL(string: item.address) else 356 | { 357 | return false 358 | } 359 | 360 | var updatedURL = url 361 | 362 | // Put the name in the default 'user' location if they've specified one 363 | if !item.username.isEmpty, !item.guest, 364 | var urlComponents = URLComponents(url: url, resolvingAgainstBaseURL: false) 365 | { 366 | urlComponents.user = item.username 367 | if let updated = urlComponents.url 368 | { 369 | updatedURL = updated 370 | } 371 | } 372 | 373 | return NSWorkspace.shared.open(updatedURL) 374 | } 375 | } 376 | 377 | // MARK: Notification handling 378 | 379 | extension DSFMountingYardController 380 | { 381 | func showConnectionNotification(for item: DSFMountingYardItem) 382 | { 383 | let notification = NSUserNotification() 384 | notification.title = NSLocalizedString("Remote server is connected", comment: "Notification message when server is successfully connected") 385 | notification.actionButtonTitle = "Show me" 386 | notification.hasActionButton = true 387 | notification.userInfo = ["mountPoint": item.mountedPoint!.path] 388 | 389 | let msg = String.localizedStringWithFormat( 390 | NSLocalizedString("The server ‘%@’ is now available", comment: "Descriptive message when server connects"), 391 | item.name 392 | ) 393 | notification.informativeText = msg 394 | notification.soundName = NSUserNotificationDefaultSoundName 395 | NSUserNotificationCenter.default.deliver(notification) 396 | } 397 | 398 | public func userNotificationCenter(_: NSUserNotificationCenter, shouldPresent _: NSUserNotification) -> Bool 399 | { 400 | return true 401 | } 402 | 403 | public func userNotificationCenter(_: NSUserNotificationCenter, didActivate notification: NSUserNotification) 404 | { 405 | if let urlPath = notification.userInfo?["mountPoint"] as? String 406 | { 407 | let url = URL(fileURLWithPath: urlPath) 408 | NSWorkspace.shared.activateFileViewerSelecting([url]) 409 | } 410 | } 411 | } 412 | -------------------------------------------------------------------------------- /Mounting Yard.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 50; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 238AAED3210FC18B008F9E18 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238AAED2210FC18B008F9E18 /* AppDelegate.swift */; }; 11 | 238AAED5210FC18C008F9E18 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 238AAED4210FC18C008F9E18 /* Assets.xcassets */; }; 12 | 238AAED8210FC18C008F9E18 /* MainMenu.xib in Resources */ = {isa = PBXBuildFile; fileRef = 238AAED6210FC18C008F9E18 /* MainMenu.xib */; }; 13 | 238AAEE4210FC18C008F9E18 /* Mounting_PointTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238AAEE3210FC18C008F9E18 /* Mounting_PointTests.swift */; }; 14 | 238AAEF0210FC361008F9E18 /* DSFMountingYardStatusMenu.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238AAEEF210FC360008F9E18 /* DSFMountingYardStatusMenu.swift */; }; 15 | 238AAEF3210FCBE4008F9E18 /* DSFMountingYardController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238AAEF2210FCBE4008F9E18 /* DSFMountingYardController.swift */; }; 16 | 238AAF07210FD7FE008F9E18 /* NetFS.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 238AAF06210FD7FE008F9E18 /* NetFS.framework */; }; 17 | 238AAF09210FF2BA008F9E18 /* DSFMountingYardItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 238AAF08210FF2BA008F9E18 /* DSFMountingYardItem.swift */; }; 18 | 23AC1D2121141BC300531FCD /* DSFMountingYardSettingsWindowController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23AC1D1F21141BC300531FCD /* DSFMountingYardSettingsWindowController.swift */; }; 19 | 23AC1D2221141BC300531FCD /* DSFMountingYardSettingsWindowController.xib in Resources */ = {isa = PBXBuildFile; fileRef = 23AC1D2021141BC300531FCD /* DSFMountingYardSettingsWindowController.xib */; }; 20 | 23AC1D242114603100531FCD /* Credits.rtf in Resources */ = {isa = PBXBuildFile; fileRef = 23AC1D232114603100531FCD /* Credits.rtf */; }; 21 | 23D0131E211575B3009737FE /* DSFImageManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 23D0131D211575B3009737FE /* DSFImageManager.swift */; }; 22 | /* End PBXBuildFile section */ 23 | 24 | /* Begin PBXContainerItemProxy section */ 25 | 238AAEE0210FC18C008F9E18 /* PBXContainerItemProxy */ = { 26 | isa = PBXContainerItemProxy; 27 | containerPortal = 238AAEC7210FC18B008F9E18 /* Project object */; 28 | proxyType = 1; 29 | remoteGlobalIDString = 238AAECE210FC18B008F9E18; 30 | remoteInfo = "Mounting Point"; 31 | }; 32 | /* End PBXContainerItemProxy section */ 33 | 34 | /* Begin PBXCopyFilesBuildPhase section */ 35 | 238AAF04210FD710008F9E18 /* Embed App Extensions */ = { 36 | isa = PBXCopyFilesBuildPhase; 37 | buildActionMask = 2147483647; 38 | dstPath = ""; 39 | dstSubfolderSpec = 13; 40 | files = ( 41 | ); 42 | name = "Embed App Extensions"; 43 | runOnlyForDeploymentPostprocessing = 0; 44 | }; 45 | /* End PBXCopyFilesBuildPhase section */ 46 | 47 | /* Begin PBXFileReference section */ 48 | 23845BAC2128CF6A00B68382 /* README.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = README.md; sourceTree = SOURCE_ROOT; }; 49 | 238AAECF210FC18B008F9E18 /* Mounting Yard.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "Mounting Yard.app"; sourceTree = BUILT_PRODUCTS_DIR; }; 50 | 238AAED2210FC18B008F9E18 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 51 | 238AAED4210FC18C008F9E18 /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; 52 | 238AAED7210FC18C008F9E18 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.xib; name = Base; path = Base.lproj/MainMenu.xib; sourceTree = ""; }; 53 | 238AAED9210FC18C008F9E18 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 54 | 238AAEDA210FC18C008F9E18 /* Mounting_Yard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Mounting_Yard.entitlements; sourceTree = ""; }; 55 | 238AAEDF210FC18C008F9E18 /* Mounting YardTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = "Mounting YardTests.xctest"; sourceTree = BUILT_PRODUCTS_DIR; }; 56 | 238AAEE3210FC18C008F9E18 /* Mounting_PointTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Mounting_PointTests.swift; sourceTree = ""; }; 57 | 238AAEE5210FC18C008F9E18 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 58 | 238AAEEF210FC360008F9E18 /* DSFMountingYardStatusMenu.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSFMountingYardStatusMenu.swift; sourceTree = ""; }; 59 | 238AAEF1210FC801008F9E18 /* Mounting Yard.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = "Mounting Yard.entitlements"; sourceTree = ""; }; 60 | 238AAEF2210FCBE4008F9E18 /* DSFMountingYardController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSFMountingYardController.swift; sourceTree = ""; }; 61 | 238AAF06210FD7FE008F9E18 /* NetFS.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = NetFS.framework; path = System/Library/Frameworks/NetFS.framework; sourceTree = SDKROOT; }; 62 | 238AAF08210FF2BA008F9E18 /* DSFMountingYardItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSFMountingYardItem.swift; sourceTree = ""; }; 63 | 23AC1D1F21141BC300531FCD /* DSFMountingYardSettingsWindowController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSFMountingYardSettingsWindowController.swift; sourceTree = ""; }; 64 | 23AC1D2021141BC300531FCD /* DSFMountingYardSettingsWindowController.xib */ = {isa = PBXFileReference; lastKnownFileType = file.xib; path = DSFMountingYardSettingsWindowController.xib; sourceTree = ""; }; 65 | 23AC1D232114603100531FCD /* Credits.rtf */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.rtf; path = Credits.rtf; sourceTree = ""; }; 66 | 23D0131D211575B3009737FE /* DSFImageManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DSFImageManager.swift; sourceTree = ""; }; 67 | /* End PBXFileReference section */ 68 | 69 | /* Begin PBXFrameworksBuildPhase section */ 70 | 238AAECC210FC18B008F9E18 /* Frameworks */ = { 71 | isa = PBXFrameworksBuildPhase; 72 | buildActionMask = 2147483647; 73 | files = ( 74 | 238AAF07210FD7FE008F9E18 /* NetFS.framework in Frameworks */, 75 | ); 76 | runOnlyForDeploymentPostprocessing = 0; 77 | }; 78 | 238AAEDC210FC18C008F9E18 /* Frameworks */ = { 79 | isa = PBXFrameworksBuildPhase; 80 | buildActionMask = 2147483647; 81 | files = ( 82 | ); 83 | runOnlyForDeploymentPostprocessing = 0; 84 | }; 85 | /* End PBXFrameworksBuildPhase section */ 86 | 87 | /* Begin PBXGroup section */ 88 | 238AAEC6210FC18B008F9E18 = { 89 | isa = PBXGroup; 90 | children = ( 91 | 238AAED1210FC18B008F9E18 /* Mounting Yard */, 92 | 238AAEE2210FC18C008F9E18 /* Mounting PointTests */, 93 | 238AAED0210FC18B008F9E18 /* Products */, 94 | 238AAF05210FD7FE008F9E18 /* Frameworks */, 95 | ); 96 | sourceTree = ""; 97 | }; 98 | 238AAED0210FC18B008F9E18 /* Products */ = { 99 | isa = PBXGroup; 100 | children = ( 101 | 238AAECF210FC18B008F9E18 /* Mounting Yard.app */, 102 | 238AAEDF210FC18C008F9E18 /* Mounting YardTests.xctest */, 103 | ); 104 | name = Products; 105 | sourceTree = ""; 106 | }; 107 | 238AAED1210FC18B008F9E18 /* Mounting Yard */ = { 108 | isa = PBXGroup; 109 | children = ( 110 | 23A3F4DA2111838800D09510 /* Resources */, 111 | 238AAEF1210FC801008F9E18 /* Mounting Yard.entitlements */, 112 | 23845BAC2128CF6A00B68382 /* README.md */, 113 | 238AAED4210FC18C008F9E18 /* Assets.xcassets */, 114 | 238AAED6210FC18C008F9E18 /* MainMenu.xib */, 115 | 238AAED9210FC18C008F9E18 /* Info.plist */, 116 | 238AAEDA210FC18C008F9E18 /* Mounting_Yard.entitlements */, 117 | 238AAED2210FC18B008F9E18 /* AppDelegate.swift */, 118 | 238AAEEF210FC360008F9E18 /* DSFMountingYardStatusMenu.swift */, 119 | 238AAEF2210FCBE4008F9E18 /* DSFMountingYardController.swift */, 120 | 238AAF08210FF2BA008F9E18 /* DSFMountingYardItem.swift */, 121 | 23AC1D1F21141BC300531FCD /* DSFMountingYardSettingsWindowController.swift */, 122 | 23D0131D211575B3009737FE /* DSFImageManager.swift */, 123 | ); 124 | path = "Mounting Yard"; 125 | sourceTree = ""; 126 | }; 127 | 238AAEE2210FC18C008F9E18 /* Mounting PointTests */ = { 128 | isa = PBXGroup; 129 | children = ( 130 | 238AAEE3210FC18C008F9E18 /* Mounting_PointTests.swift */, 131 | 238AAEE5210FC18C008F9E18 /* Info.plist */, 132 | ); 133 | path = "Mounting PointTests"; 134 | sourceTree = ""; 135 | }; 136 | 238AAF05210FD7FE008F9E18 /* Frameworks */ = { 137 | isa = PBXGroup; 138 | children = ( 139 | 238AAF06210FD7FE008F9E18 /* NetFS.framework */, 140 | ); 141 | name = Frameworks; 142 | sourceTree = ""; 143 | }; 144 | 23A3F4DA2111838800D09510 /* Resources */ = { 145 | isa = PBXGroup; 146 | children = ( 147 | 23AC1D2021141BC300531FCD /* DSFMountingYardSettingsWindowController.xib */, 148 | 23AC1D232114603100531FCD /* Credits.rtf */, 149 | ); 150 | path = Resources; 151 | sourceTree = ""; 152 | }; 153 | /* End PBXGroup section */ 154 | 155 | /* Begin PBXNativeTarget section */ 156 | 238AAECE210FC18B008F9E18 /* Mounting Yard */ = { 157 | isa = PBXNativeTarget; 158 | buildConfigurationList = 238AAEE8210FC18C008F9E18 /* Build configuration list for PBXNativeTarget "Mounting Yard" */; 159 | buildPhases = ( 160 | 238AAECB210FC18B008F9E18 /* Sources */, 161 | 238AAECC210FC18B008F9E18 /* Frameworks */, 162 | 238AAECD210FC18B008F9E18 /* Resources */, 163 | 238AAF04210FD710008F9E18 /* Embed App Extensions */, 164 | ); 165 | buildRules = ( 166 | ); 167 | dependencies = ( 168 | ); 169 | name = "Mounting Yard"; 170 | productName = "Mounting Point"; 171 | productReference = 238AAECF210FC18B008F9E18 /* Mounting Yard.app */; 172 | productType = "com.apple.product-type.application"; 173 | }; 174 | 238AAEDE210FC18C008F9E18 /* Mounting YardTests */ = { 175 | isa = PBXNativeTarget; 176 | buildConfigurationList = 238AAEEB210FC18C008F9E18 /* Build configuration list for PBXNativeTarget "Mounting YardTests" */; 177 | buildPhases = ( 178 | 238AAEDB210FC18C008F9E18 /* Sources */, 179 | 238AAEDC210FC18C008F9E18 /* Frameworks */, 180 | 238AAEDD210FC18C008F9E18 /* Resources */, 181 | ); 182 | buildRules = ( 183 | ); 184 | dependencies = ( 185 | 238AAEE1210FC18C008F9E18 /* PBXTargetDependency */, 186 | ); 187 | name = "Mounting YardTests"; 188 | productName = "Mounting PointTests"; 189 | productReference = 238AAEDF210FC18C008F9E18 /* Mounting YardTests.xctest */; 190 | productType = "com.apple.product-type.bundle.unit-test"; 191 | }; 192 | /* End PBXNativeTarget section */ 193 | 194 | /* Begin PBXProject section */ 195 | 238AAEC7210FC18B008F9E18 /* Project object */ = { 196 | isa = PBXProject; 197 | attributes = { 198 | LastSwiftUpdateCheck = 0940; 199 | LastUpgradeCheck = 0940; 200 | ORGANIZATIONNAME = "Darren Ford"; 201 | TargetAttributes = { 202 | 238AAECE210FC18B008F9E18 = { 203 | CreatedOnToolsVersion = 9.4.1; 204 | SystemCapabilities = { 205 | com.apple.ApplicationGroups.Mac = { 206 | enabled = 0; 207 | }; 208 | com.apple.Sandbox = { 209 | enabled = 1; 210 | }; 211 | }; 212 | }; 213 | 238AAEDE210FC18C008F9E18 = { 214 | CreatedOnToolsVersion = 9.4.1; 215 | TestTargetID = 238AAECE210FC18B008F9E18; 216 | }; 217 | }; 218 | }; 219 | buildConfigurationList = 238AAECA210FC18B008F9E18 /* Build configuration list for PBXProject "Mounting Yard" */; 220 | compatibilityVersion = "Xcode 9.3"; 221 | developmentRegion = en; 222 | hasScannedForEncodings = 0; 223 | knownRegions = ( 224 | en, 225 | Base, 226 | ); 227 | mainGroup = 238AAEC6210FC18B008F9E18; 228 | productRefGroup = 238AAED0210FC18B008F9E18 /* Products */; 229 | projectDirPath = ""; 230 | projectRoot = ""; 231 | targets = ( 232 | 238AAECE210FC18B008F9E18 /* Mounting Yard */, 233 | 238AAEDE210FC18C008F9E18 /* Mounting YardTests */, 234 | ); 235 | }; 236 | /* End PBXProject section */ 237 | 238 | /* Begin PBXResourcesBuildPhase section */ 239 | 238AAECD210FC18B008F9E18 /* Resources */ = { 240 | isa = PBXResourcesBuildPhase; 241 | buildActionMask = 2147483647; 242 | files = ( 243 | 23AC1D242114603100531FCD /* Credits.rtf in Resources */, 244 | 238AAED5210FC18C008F9E18 /* Assets.xcassets in Resources */, 245 | 238AAED8210FC18C008F9E18 /* MainMenu.xib in Resources */, 246 | 23AC1D2221141BC300531FCD /* DSFMountingYardSettingsWindowController.xib in Resources */, 247 | ); 248 | runOnlyForDeploymentPostprocessing = 0; 249 | }; 250 | 238AAEDD210FC18C008F9E18 /* Resources */ = { 251 | isa = PBXResourcesBuildPhase; 252 | buildActionMask = 2147483647; 253 | files = ( 254 | ); 255 | runOnlyForDeploymentPostprocessing = 0; 256 | }; 257 | /* End PBXResourcesBuildPhase section */ 258 | 259 | /* Begin PBXSourcesBuildPhase section */ 260 | 238AAECB210FC18B008F9E18 /* Sources */ = { 261 | isa = PBXSourcesBuildPhase; 262 | buildActionMask = 2147483647; 263 | files = ( 264 | 23AC1D2121141BC300531FCD /* DSFMountingYardSettingsWindowController.swift in Sources */, 265 | 238AAEF0210FC361008F9E18 /* DSFMountingYardStatusMenu.swift in Sources */, 266 | 238AAF09210FF2BA008F9E18 /* DSFMountingYardItem.swift in Sources */, 267 | 23D0131E211575B3009737FE /* DSFImageManager.swift in Sources */, 268 | 238AAED3210FC18B008F9E18 /* AppDelegate.swift in Sources */, 269 | 238AAEF3210FCBE4008F9E18 /* DSFMountingYardController.swift in Sources */, 270 | ); 271 | runOnlyForDeploymentPostprocessing = 0; 272 | }; 273 | 238AAEDB210FC18C008F9E18 /* Sources */ = { 274 | isa = PBXSourcesBuildPhase; 275 | buildActionMask = 2147483647; 276 | files = ( 277 | 238AAEE4210FC18C008F9E18 /* Mounting_PointTests.swift in Sources */, 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | }; 281 | /* End PBXSourcesBuildPhase section */ 282 | 283 | /* Begin PBXTargetDependency section */ 284 | 238AAEE1210FC18C008F9E18 /* PBXTargetDependency */ = { 285 | isa = PBXTargetDependency; 286 | target = 238AAECE210FC18B008F9E18 /* Mounting Yard */; 287 | targetProxy = 238AAEE0210FC18C008F9E18 /* PBXContainerItemProxy */; 288 | }; 289 | /* End PBXTargetDependency section */ 290 | 291 | /* Begin PBXVariantGroup section */ 292 | 238AAED6210FC18C008F9E18 /* MainMenu.xib */ = { 293 | isa = PBXVariantGroup; 294 | children = ( 295 | 238AAED7210FC18C008F9E18 /* Base */, 296 | ); 297 | name = MainMenu.xib; 298 | sourceTree = ""; 299 | }; 300 | /* End PBXVariantGroup section */ 301 | 302 | /* Begin XCBuildConfiguration section */ 303 | 238AAEE6210FC18C008F9E18 /* Debug */ = { 304 | isa = XCBuildConfiguration; 305 | buildSettings = { 306 | ALWAYS_SEARCH_USER_PATHS = NO; 307 | CLANG_ANALYZER_NONNULL = YES; 308 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 309 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 310 | CLANG_CXX_LIBRARY = "libc++"; 311 | CLANG_ENABLE_MODULES = YES; 312 | CLANG_ENABLE_OBJC_ARC = YES; 313 | CLANG_ENABLE_OBJC_WEAK = YES; 314 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 315 | CLANG_WARN_BOOL_CONVERSION = YES; 316 | CLANG_WARN_COMMA = YES; 317 | CLANG_WARN_CONSTANT_CONVERSION = YES; 318 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 319 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 320 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 321 | CLANG_WARN_EMPTY_BODY = YES; 322 | CLANG_WARN_ENUM_CONVERSION = YES; 323 | CLANG_WARN_INFINITE_RECURSION = YES; 324 | CLANG_WARN_INT_CONVERSION = YES; 325 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 326 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 327 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 328 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 329 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 330 | CLANG_WARN_STRICT_PROTOTYPES = YES; 331 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 332 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 333 | CLANG_WARN_UNREACHABLE_CODE = YES; 334 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 335 | CODE_SIGN_IDENTITY = "Mac Developer"; 336 | COPY_PHASE_STRIP = NO; 337 | DEBUG_INFORMATION_FORMAT = dwarf; 338 | ENABLE_STRICT_OBJC_MSGSEND = YES; 339 | ENABLE_TESTABILITY = YES; 340 | GCC_C_LANGUAGE_STANDARD = gnu11; 341 | GCC_DYNAMIC_NO_PIC = NO; 342 | GCC_NO_COMMON_BLOCKS = YES; 343 | GCC_OPTIMIZATION_LEVEL = 0; 344 | GCC_PREPROCESSOR_DEFINITIONS = ( 345 | "DEBUG=1", 346 | "$(inherited)", 347 | ); 348 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 349 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 350 | GCC_WARN_UNDECLARED_SELECTOR = YES; 351 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 352 | GCC_WARN_UNUSED_FUNCTION = YES; 353 | GCC_WARN_UNUSED_VARIABLE = YES; 354 | MACOSX_DEPLOYMENT_TARGET = 10.10; 355 | MTL_ENABLE_DEBUG_INFO = YES; 356 | ONLY_ACTIVE_ARCH = YES; 357 | SDKROOT = macosx; 358 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 359 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 360 | }; 361 | name = Debug; 362 | }; 363 | 238AAEE7210FC18C008F9E18 /* Release */ = { 364 | isa = XCBuildConfiguration; 365 | buildSettings = { 366 | ALWAYS_SEARCH_USER_PATHS = NO; 367 | CLANG_ANALYZER_NONNULL = YES; 368 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 369 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 370 | CLANG_CXX_LIBRARY = "libc++"; 371 | CLANG_ENABLE_MODULES = YES; 372 | CLANG_ENABLE_OBJC_ARC = YES; 373 | CLANG_ENABLE_OBJC_WEAK = YES; 374 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 375 | CLANG_WARN_BOOL_CONVERSION = YES; 376 | CLANG_WARN_COMMA = YES; 377 | CLANG_WARN_CONSTANT_CONVERSION = YES; 378 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 379 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 380 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 381 | CLANG_WARN_EMPTY_BODY = YES; 382 | CLANG_WARN_ENUM_CONVERSION = YES; 383 | CLANG_WARN_INFINITE_RECURSION = YES; 384 | CLANG_WARN_INT_CONVERSION = YES; 385 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 386 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 387 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 388 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 389 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 390 | CLANG_WARN_STRICT_PROTOTYPES = YES; 391 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 392 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 393 | CLANG_WARN_UNREACHABLE_CODE = YES; 394 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 395 | CODE_SIGN_IDENTITY = "Mac Developer"; 396 | COPY_PHASE_STRIP = NO; 397 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 398 | ENABLE_NS_ASSERTIONS = NO; 399 | ENABLE_STRICT_OBJC_MSGSEND = YES; 400 | GCC_C_LANGUAGE_STANDARD = gnu11; 401 | GCC_NO_COMMON_BLOCKS = YES; 402 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 403 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 404 | GCC_WARN_UNDECLARED_SELECTOR = YES; 405 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 406 | GCC_WARN_UNUSED_FUNCTION = YES; 407 | GCC_WARN_UNUSED_VARIABLE = YES; 408 | MACOSX_DEPLOYMENT_TARGET = 10.10; 409 | MTL_ENABLE_DEBUG_INFO = NO; 410 | SDKROOT = macosx; 411 | SWIFT_COMPILATION_MODE = wholemodule; 412 | SWIFT_OPTIMIZATION_LEVEL = "-O"; 413 | }; 414 | name = Release; 415 | }; 416 | 238AAEE9210FC18C008F9E18 /* Debug */ = { 417 | isa = XCBuildConfiguration; 418 | buildSettings = { 419 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 420 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 421 | CODE_SIGN_ENTITLEMENTS = "Mounting Yard/Mounting Yard.entitlements"; 422 | CODE_SIGN_IDENTITY = "Mac Developer"; 423 | CODE_SIGN_STYLE = Automatic; 424 | COMBINE_HIDPI_IMAGES = YES; 425 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 426 | INFOPLIST_FILE = "$(SRCROOT)/Mounting Yard/Info.plist"; 427 | LD_RUNPATH_SEARCH_PATHS = ( 428 | "$(inherited)", 429 | "@executable_path/../Frameworks", 430 | ); 431 | MACOSX_DEPLOYMENT_TARGET = 10.11; 432 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.Mounting-Yard"; 433 | PRODUCT_NAME = "$(TARGET_NAME)"; 434 | PROVISIONING_PROFILE = ""; 435 | PROVISIONING_PROFILE_SPECIFIER = ""; 436 | SWIFT_VERSION = 4.0; 437 | }; 438 | name = Debug; 439 | }; 440 | 238AAEEA210FC18C008F9E18 /* Release */ = { 441 | isa = XCBuildConfiguration; 442 | buildSettings = { 443 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 444 | ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; 445 | CODE_SIGN_ENTITLEMENTS = "Mounting Yard/Mounting Yard.entitlements"; 446 | CODE_SIGN_IDENTITY = "Mac Developer"; 447 | CODE_SIGN_STYLE = Automatic; 448 | COMBINE_HIDPI_IMAGES = YES; 449 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 450 | INFOPLIST_FILE = "$(SRCROOT)/Mounting Yard/Info.plist"; 451 | LD_RUNPATH_SEARCH_PATHS = ( 452 | "$(inherited)", 453 | "@executable_path/../Frameworks", 454 | ); 455 | MACOSX_DEPLOYMENT_TARGET = 10.11; 456 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.Mounting-Yard"; 457 | PRODUCT_NAME = "$(TARGET_NAME)"; 458 | PROVISIONING_PROFILE = ""; 459 | PROVISIONING_PROFILE_SPECIFIER = ""; 460 | SWIFT_VERSION = 4.0; 461 | }; 462 | name = Release; 463 | }; 464 | 238AAEEC210FC18C008F9E18 /* Debug */ = { 465 | isa = XCBuildConfiguration; 466 | buildSettings = { 467 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 468 | BUNDLE_LOADER = "$(TEST_HOST)"; 469 | CODE_SIGN_STYLE = Automatic; 470 | COMBINE_HIDPI_IMAGES = YES; 471 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 472 | INFOPLIST_FILE = "Mounting PointTests/Info.plist"; 473 | LD_RUNPATH_SEARCH_PATHS = ( 474 | "$(inherited)", 475 | "@executable_path/../Frameworks", 476 | "@loader_path/../Frameworks", 477 | ); 478 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.Mounting-PointTests"; 479 | PRODUCT_NAME = "$(TARGET_NAME)"; 480 | SWIFT_VERSION = 4.0; 481 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mounting Yard.app/Contents/MacOS/Mounting Yard"; 482 | }; 483 | name = Debug; 484 | }; 485 | 238AAEED210FC18C008F9E18 /* Release */ = { 486 | isa = XCBuildConfiguration; 487 | buildSettings = { 488 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES; 489 | BUNDLE_LOADER = "$(TEST_HOST)"; 490 | CODE_SIGN_STYLE = Automatic; 491 | COMBINE_HIDPI_IMAGES = YES; 492 | DEVELOPMENT_TEAM = 3L6RK3LGGW; 493 | INFOPLIST_FILE = "Mounting PointTests/Info.plist"; 494 | LD_RUNPATH_SEARCH_PATHS = ( 495 | "$(inherited)", 496 | "@executable_path/../Frameworks", 497 | "@loader_path/../Frameworks", 498 | ); 499 | PRODUCT_BUNDLE_IDENTIFIER = "com.darrenford.Mounting-PointTests"; 500 | PRODUCT_NAME = "$(TARGET_NAME)"; 501 | SWIFT_VERSION = 4.0; 502 | TEST_HOST = "$(BUILT_PRODUCTS_DIR)/Mounting Yard.app/Contents/MacOS/Mounting Yard"; 503 | }; 504 | name = Release; 505 | }; 506 | /* End XCBuildConfiguration section */ 507 | 508 | /* Begin XCConfigurationList section */ 509 | 238AAECA210FC18B008F9E18 /* Build configuration list for PBXProject "Mounting Yard" */ = { 510 | isa = XCConfigurationList; 511 | buildConfigurations = ( 512 | 238AAEE6210FC18C008F9E18 /* Debug */, 513 | 238AAEE7210FC18C008F9E18 /* Release */, 514 | ); 515 | defaultConfigurationIsVisible = 0; 516 | defaultConfigurationName = Release; 517 | }; 518 | 238AAEE8210FC18C008F9E18 /* Build configuration list for PBXNativeTarget "Mounting Yard" */ = { 519 | isa = XCConfigurationList; 520 | buildConfigurations = ( 521 | 238AAEE9210FC18C008F9E18 /* Debug */, 522 | 238AAEEA210FC18C008F9E18 /* Release */, 523 | ); 524 | defaultConfigurationIsVisible = 0; 525 | defaultConfigurationName = Release; 526 | }; 527 | 238AAEEB210FC18C008F9E18 /* Build configuration list for PBXNativeTarget "Mounting YardTests" */ = { 528 | isa = XCConfigurationList; 529 | buildConfigurations = ( 530 | 238AAEEC210FC18C008F9E18 /* Debug */, 531 | 238AAEED210FC18C008F9E18 /* Release */, 532 | ); 533 | defaultConfigurationIsVisible = 0; 534 | defaultConfigurationName = Release; 535 | }; 536 | /* End XCConfigurationList section */ 537 | }; 538 | rootObject = 238AAEC7210FC18B008F9E18 /* Project object */; 539 | } 540 | -------------------------------------------------------------------------------- /Mounting Yard/Resources/DSFMountingYardSettingsWindowController.xib: -------------------------------------------------------------------------------- 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 153 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 178 | 189 | 190 | 191 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | NSNegateBoolean 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | -------------------------------------------------------------------------------- /Mounting Yard/Base.lproj/MainMenu.xib: -------------------------------------------------------------------------------- 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 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | 219 | 220 | 221 | 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 230 | 231 | 232 | 233 | 234 | 235 | 236 | 237 | 238 | 239 | 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 248 | 249 | 250 | 251 | 252 | 253 | 254 | 255 | 256 | 257 | 258 | 259 | 260 | 261 | 262 | 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | 271 | 272 | 273 | 274 | 275 | 276 | 277 | 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 292 | 293 | 294 | 295 | 296 | 297 | 298 | 299 | 300 | 301 | 302 | 303 | 304 | 305 | 306 | 307 | 308 | 309 | 310 | 311 | 312 | 313 | 314 | 315 | 316 | 317 | 318 | 319 | 320 | 321 | 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | 333 | 334 | 335 | 336 | 337 | 338 | 339 | 340 | 341 | 342 | 343 | 344 | 345 | 346 | 347 | 348 | 349 | 350 | 351 | 352 | 353 | 354 | 355 | 356 | 357 | 358 | 359 | 360 | 361 | 362 | 363 | 364 | 365 | 366 | 367 | 368 | 369 | 370 | 371 | 372 | 373 | 374 | 375 | 376 | 377 | 378 | 379 | 380 | 381 | 382 | 383 | 384 | 385 | 386 | 387 | 388 | 389 | 390 | 391 | 392 | 393 | 394 | 395 | 396 | 397 | 398 | 399 | 400 | 401 | 402 | 403 | 404 | 405 | 406 | 407 | 408 | 409 | 410 | 411 | 412 | 413 | 414 | 415 | 416 | 417 | 418 | 419 | 420 | 421 | 422 | 423 | 424 | 425 | 426 | 427 | 428 | 429 | 430 | 431 | 432 | 433 | 434 | 435 | 436 | 437 | 438 | 439 | 440 | 441 | 442 | 443 | 444 | 445 | 446 | 447 | 448 | 449 | 450 | 451 | 452 | 453 | 454 | 455 | 456 | 457 | 458 | 459 | 460 | 461 | 462 | 463 | 464 | 465 | 466 | 467 | 468 | 469 | 470 | 471 | 472 | 473 | 474 | 475 | 476 | 477 | 478 | 479 | 480 | 481 | 482 | 483 | 484 | 485 | 486 | 487 | 488 | 489 | 490 | 491 | 492 | 493 | 494 | 495 | 496 | 497 | 498 | 499 | 500 | 501 | 502 | 503 | 504 | 505 | 506 | 507 | 508 | 509 | 510 | 511 | 512 | 513 | 514 | 515 | 516 | 517 | 518 | 519 | 520 | 521 | 522 | 523 | 524 | 525 | 526 | 527 | 528 | 529 | 530 | 531 | 532 | 533 | 534 | 535 | 536 | 537 | 538 | 539 | Default 540 | 541 | 542 | 543 | 544 | 545 | 546 | Left to Right 547 | 548 | 549 | 550 | 551 | 552 | 553 | Right to Left 554 | 555 | 556 | 557 | 558 | 559 | 560 | 561 | 562 | 563 | 564 | Default 565 | 566 | 567 | 568 | 569 | 570 | 571 | Left to Right 572 | 573 | 574 | 575 | 576 | 577 | 578 | Right to Left 579 | 580 | 581 | 582 | 583 | 584 | 585 | 586 | 587 | 588 | 589 | 590 | 591 | 592 | 593 | 594 | 595 | 596 | 597 | 598 | 599 | 600 | 601 | 602 | 603 | 604 | 605 | 606 | 607 | 608 | 609 | 610 | 611 | 612 | 613 | 614 | 615 | 616 | 617 | 618 | 619 | 620 | 621 | 622 | 623 | 624 | 625 | 626 | 627 | 628 | 629 | 630 | 631 | 632 | 633 | 634 | 635 | 636 | 637 | 638 | 639 | 640 | 641 | 642 | 643 | 644 | 645 | 646 | 647 | 648 | 649 | 650 | 651 | 652 | 653 | 654 | 655 | 656 | 657 | 658 | 659 | 660 | 661 | 662 | 663 | 664 | 665 | 666 | 667 | 668 | 669 | 670 | 671 | 672 | 673 | 674 | 675 | 676 | 677 | 678 | 679 | 680 | 681 | 682 | 683 | 684 | 685 | 686 | 687 | 688 | 689 | 690 | 691 | 692 | 693 | 694 | 695 | 696 | 697 | 698 | 699 | 700 | 701 | 702 | --------------------------------------------------------------------------------