├── README.md
├── Releases
└── WWDC.srt.zip
├── ScreenShots
├── Session01.png
├── Session02.png
├── TextFile.png
└── VideoLink.png
├── WWDC.srt.xcodeproj
├── project.pbxproj
├── project.xcworkspace
│ ├── contents.xcworkspacedata
│ ├── xcshareddata
│ │ └── IDEWorkspaceChecks.plist
│ └── xcuserdata
│ │ └── SeyedSamad.xcuserdatad
│ │ └── IDEFindNavigatorScopes.plist
├── xcshareddata
│ ├── xcbaselines
│ │ └── E4CDBE55220BF3F800CE6A9C.xcbaseline
│ │ │ ├── F17008CD-99B2-4B1D-8F0F-7472A553CA00.plist
│ │ │ └── Info.plist
│ └── xcdebugger
│ │ └── Breakpoints_v2.xcbkptlist
└── xcuserdata
│ └── SeyedSamad.xcuserdatad
│ ├── xcdebugger
│ └── Breakpoints_v2.xcbkptlist
│ └── xcschemes
│ ├── WWDCSubGetter.xcscheme
│ └── xcschememanagement.plist
├── WWDC.srtTests
├── 505.html
├── 708.html
├── Info.plist
└── WWDC_srtTests.swift
└── WWDCSubGetter
├── AppDelegate.swift
├── Array+Extensions.swift
├── Assets.xcassets
├── AppIcon.appiconset
│ ├── Contents.json
│ ├── Icon_128x128.png
│ ├── Icon_128x128@2x.png
│ ├── Icon_16x16.png
│ ├── Icon_16x16@2x.png
│ ├── Icon_256x256.png
│ ├── Icon_256x256@2x.png
│ ├── Icon_32x32.png
│ ├── Icon_32x32@2x.png
│ ├── Icon_512x512.png
│ └── Icon_512x512@2x.png
└── Contents.json
├── Base.lproj
└── Main.storyboard
├── ConvertToSubtitleOperation.swift
├── DownloadHtmlVideoPageOperation.swift
├── DownloadM3U8Operation.swift
├── DownloadWebvttOperation.swift
├── ExportLinksOperation.swift
├── ExportSrtFileOperation.swift
├── FetchHtmlVideoPagesOperation.swift
├── FetchSubtitilesOperation.swift
├── FetchWebvttsOperation.swift
├── GetHtmlSessionsListOperation.swift
├── GetLinksOperation.swift
├── GetSessionsListOperation.swift
├── GetSubtitlesOperation.swift
├── Info.plist
├── LinksModel.swift
├── MainViewController.swift
├── Model.swift
├── Operations
├── Conditions
│ ├── MutuallyExclusive.swift
│ ├── OperationCondition.swift
│ ├── OperationErrors.swift
│ └── ReachabilityCondition.swift
├── Foundation.Operation+Operation.swift
├── NSLock+Operations.swift
├── Observers
│ ├── BlockObserver.swift
│ ├── NetworkObserver.swift
│ └── OperationObserver.swift
├── OperationQueue
│ ├── ExclusivityController.swift
│ └── OperationQueue.swift
└── Operations
│ ├── AlertOperation.swift
│ ├── BlockOperation.swift
│ ├── GroupOperation.swift
│ ├── Operation.swift
│ └── URLSessionTaskOperation.swift
├── ParseHtmlVideoPageOperation.swift
├── ParseSessionsListOperation.swift
├── Presenter.swift
├── Resources
├── WWDC2013_links.txt
├── WWDC2014_links.txt
├── WWDC2015_links.txt
├── WWDC2016_links.txt
└── WWDC2017_links.txt
├── String+Extensions.swift
├── Subtitle.swift
├── SubtitlesProgress.swift
├── TextFileView.swift
├── WWDC2019.swift
├── WWDC2020.swift
├── WWDC2021.swift
├── WWDCVideosController.swift
├── WWDCYear.swift
└── Webvtt.swift
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 | # WWDC.srt
25 | #### An app for Download WWDC subtitles
26 |
27 | ### Whats New:
28 |
29 | **version 1.6.6:**
30 |
31 | - Supports WWDC 2024
32 |
33 | **version 1.6.5:**
34 |
35 | - Supports WWDC 2022 and WWDC 2023
36 |
37 | **version 1.6.4:**
38 |
39 | - Supports WWDC 2020
40 | - Fixed a bug which doesn't download some of subtitles and video links correctly
41 |
42 | **version 1.6.2:**
43 |
44 | - Now can download link of Sample codes for WWDC 2019, 2018, 2017, 2016 videos
45 | - Some minor bugs fixed
46 |
47 | **version 1.6.1:**
48 |
49 | - WWDC 2019 Added.
50 |
51 | **version 1.6.0:**
52 |
53 | - Performance optimized. Now you get links and subtitles much faster.
54 | - Now supports Tech Talks Video's links
55 |
56 | **version 1.5.3:**
57 |
58 | - Fixed an issue that failed to get all links after you selecting a special link to get
59 |
60 | **version 1.5.2:**
61 |
62 | - Fixed an issue that downloads wrong subtitle for selected session in some cases
63 |
64 | **version 1.5.1:**
65 |
66 | - Now supports WWDC 2018 video's links
67 |
68 |
69 | - Now you can get links of videos, pdfs and sample codes for each session you want and even for all sessions at once!
70 |
71 | - Now the app opens the destination address of your desired data in finder, after downloading them.
72 |
73 | - Some minor bugs fixed
74 |
75 |
76 | **version 1.0.1:**
77 |
78 | - Now supports Fall 2017 video's links
79 |
80 | ## Intro
81 | WWDC.srt allows you to download subtitle for each WWDC session video since 2013 in (**srt**) format.
82 |
83 | ⬇️ If you just want to download the latest release, go to [this link](./Releases/WWDC.srt.zip).
84 |
85 | ## Session
86 |
87 | In this tab you can choose ( or search session number of ) your favorite WWDC Session video from the list and download it's subtitle or data links by clicking get button. Also you can download all sessions subtitles or data links alltogether by choosing (All Sessions) radio button.
88 | Data links are include videos links (HD or SD depending on your choice), pdf links and sample code links.
89 |
90 | 
91 |
92 |
93 | 
94 |
95 |
96 | ## Video Link
97 |
98 | In this tab you can paste WWDC video link like:
99 |
100 | ` https://devstreaming-cdn.apple.com/videos/tutorials/20170912/201qy4t11tjpm/building_apps_for_iphone_x/building_apps_for_iphone_x_hd.mp4?dl=1 `
101 |
102 | into text field and download it's subtitle.
103 |
104 | 
105 |
106 |
107 | ## Text File
108 |
109 | In this tab you can just drag a text file which contains a bunch of your favorite WWDC Video's links into the view and download their subtitles altogether.
110 |
111 | 
112 |
113 | ## Building the app
114 |
115 | **Building requires Xcode 9 or later.**
116 |
117 | Just clone this branch and run the project in xcode 9.
118 |
--------------------------------------------------------------------------------
/Releases/WWDC.srt.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/Releases/WWDC.srt.zip
--------------------------------------------------------------------------------
/ScreenShots/Session01.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/ScreenShots/Session01.png
--------------------------------------------------------------------------------
/ScreenShots/Session02.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/ScreenShots/Session02.png
--------------------------------------------------------------------------------
/ScreenShots/TextFile.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/ScreenShots/TextFile.png
--------------------------------------------------------------------------------
/ScreenShots/VideoLink.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/ScreenShots/VideoLink.png
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/project.xcworkspace/contents.xcworkspacedata:
--------------------------------------------------------------------------------
1 |
2 |
4 |
6 |
7 |
8 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | IDEDidComputeMac32BitWarning
6 |
7 |
8 |
9 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/project.xcworkspace/xcuserdata/SeyedSamad.xcuserdatad/IDEFindNavigatorScopes.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/xcshareddata/xcbaselines/E4CDBE55220BF3F800CE6A9C.xcbaseline/F17008CD-99B2-4B1D-8F0F-7472A553CA00.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | classNames
6 |
7 | WWDC_srtTests
8 |
9 | testExample()
10 |
11 | com.apple.XCTPerformanceMetric_WallClockTime
12 |
13 | baselineAverage
14 | 0.00158
15 | baselineIntegrationDisplayName
16 | Local Baseline
17 |
18 |
19 | testPdfParsing()
20 |
21 | com.apple.XCTPerformanceMetric_WallClockTime
22 |
23 | baselineAverage
24 | 0.0018097
25 | baselineIntegrationDisplayName
26 | Local Baseline
27 |
28 |
29 | testZipParsing()
30 |
31 | com.apple.XCTPerformanceMetric_WallClockTime
32 |
33 | baselineAverage
34 | 0.989
35 | baselineIntegrationDisplayName
36 | Local Baseline
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/xcshareddata/xcbaselines/E4CDBE55220BF3F800CE6A9C.xcbaseline/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | runDestinationsByUUID
6 |
7 | F17008CD-99B2-4B1D-8F0F-7472A553CA00
8 |
9 | localComputer
10 |
11 | busSpeedInMHz
12 | 100
13 | cpuCount
14 | 1
15 | cpuKind
16 | Intel Core i7
17 | cpuSpeedInMHz
18 | 2500
19 | logicalCPUCoresPerPackage
20 | 8
21 | modelCode
22 | MacBookPro11,5
23 | physicalCPUCoresPerPackage
24 | 4
25 | platformIdentifier
26 | com.apple.platform.macosx
27 |
28 | targetArchitecture
29 | x86_64
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/xcshareddata/xcdebugger/Breakpoints_v2.xcbkptlist:
--------------------------------------------------------------------------------
1 |
2 |
6 |
7 |
9 |
22 |
23 |
25 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/xcuserdata/SeyedSamad.xcuserdatad/xcschemes/WWDCSubGetter.xcscheme:
--------------------------------------------------------------------------------
1 |
2 |
5 |
8 |
9 |
15 |
21 |
22 |
23 |
24 |
25 |
30 |
31 |
37 |
38 |
39 |
40 |
42 |
48 |
49 |
50 |
52 |
58 |
59 |
60 |
61 |
62 |
73 |
75 |
81 |
82 |
83 |
84 |
90 |
92 |
98 |
99 |
100 |
101 |
103 |
104 |
107 |
108 |
109 |
--------------------------------------------------------------------------------
/WWDC.srt.xcodeproj/xcuserdata/SeyedSamad.xcuserdatad/xcschemes/xcschememanagement.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | SchemeUserState
6 |
7 | WWDCSubGetter.xcscheme
8 |
9 | orderHint
10 | 0
11 |
12 |
13 | SuppressBuildableAutocreation
14 |
15 | E4F327C31F406EA80018B524
16 |
17 | primary
18 |
19 |
20 | E4F327D41F406EA80018B524
21 |
22 | primary
23 |
24 |
25 | E4F327DF1F406EA80018B524
26 |
27 | primary
28 |
29 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/WWDC.srtTests/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 |
--------------------------------------------------------------------------------
/WWDC.srtTests/WWDC_srtTests.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WWDC_srtTests.swift
3 | // WWDC.srtTests
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 2/7/19.
6 | // Copyright © 2019 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import XCTest
10 | @testable import WWDC_srt
11 |
12 | class WWDC_srtTests: XCTestCase {
13 |
14 | let presenter = Presenter()
15 |
16 | override func setUp() {
17 | // Put setup code here. This method is called before the invocation of each test method in the class.
18 | }
19 |
20 | override func tearDown() {
21 | // Put teardown code here. This method is called after the invocation of each test method in the class.
22 | }
23 |
24 | func testVideoParsing() {
25 | let fileName = "708"
26 | let bundle = Bundle(for: type(of: self))
27 | let url = bundle.url(forResource: fileName, withExtension: "html")!
28 | let data = try! Data(contentsOf: url, options: Data.ReadingOptions.mappedIfSafe)
29 |
30 | let htmlText = String.init(data: data, encoding:
31 | .utf8)!
32 |
33 | self.measure {
34 | let videoURLString = WWDCVideosController.getHDorSDdURLs(fromHTML: htmlText, format: .sd)
35 | print(videoURLString)
36 | //
37 | // let pdfURLStrings = WWDCVideosController.getPDFResourceURL(fromHTML: htmlText)
38 | // let sampleCodesURLStrings = WWDCVideosController.getSampleCodeURL(fromHTML: htmlText)
39 | // let sampleCodesURLStrings2 = WWDCVideosController.getSampleCodeURL2(fromHTML: htmlText)
40 | }
41 |
42 | }
43 |
44 | func testPdfParsing() {
45 | let fileName = "708"
46 | let bundle = Bundle(for: type(of: self))
47 | let url = bundle.url(forResource: fileName, withExtension: "html")!
48 | let data = try! Data(contentsOf: url, options: Data.ReadingOptions.mappedIfSafe)
49 |
50 | let htmlText = String.init(data: data, encoding:
51 | .utf8)!
52 |
53 | self.measure {
54 | let pdfURLStrings = WWDCVideosController.getPDFResourceURL(fromHTML: htmlText)
55 | print(pdfURLStrings)
56 |
57 | // let sampleCodesURLStrings = WWDCVideosController.getSampleCodeURL(fromHTML: htmlText)
58 | // let sampleCodesURLStrings2 = WWDCVideosController.getSampleCodeURL2(fromHTML: htmlText)
59 | }
60 | }
61 |
62 | func testZipParsing() {
63 |
64 | let fileName = "505"
65 | let bundle = Bundle(for: type(of: self))
66 | let url = bundle.url(forResource: fileName, withExtension: "html")!
67 | let data = try! Data(contentsOf: url, options: Data.ReadingOptions.mappedIfSafe)
68 |
69 | let htmlText = String.init(data: data, encoding:
70 | .utf8)!
71 |
72 | self.measure {
73 | let sampleCodesURLStrings = WWDCVideosController.getSampleCodeURL(fromHTML: htmlText)
74 | print("sampleCodesURLStrings: ", sampleCodesURLStrings.count)
75 |
76 | let sampleCodesURLStrings2 = WWDCVideosController.getSampleCodeURL2(fromHTML: htmlText)
77 | print("sampleCodesURLStrings2: ", sampleCodesURLStrings2.count)
78 | }
79 |
80 | }
81 |
82 |
83 | }
84 |
--------------------------------------------------------------------------------
/WWDCSubGetter/AppDelegate.swift:
--------------------------------------------------------------------------------
1 | //
2 | // AppDelegate.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 5/22/1396 AP.
6 | // Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | @NSApplicationMain
12 | class AppDelegate: NSObject, NSApplicationDelegate {
13 |
14 |
15 | func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
16 | if !flag {
17 | for window in sender.windows {
18 | window.windowController?.showWindow(self)
19 | }
20 | }
21 |
22 | return true
23 | }
24 |
25 | }
26 |
27 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Array+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Array+Extensions.swift
3 |
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 24/7/1396 AP.
6 | // Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | extension Array where Element: Hashable {
13 |
14 | /// A method which return unique values of array.
15 | func removingDuplicates() -> [Element] {
16 | var addedDict = [Element: Bool]()
17 |
18 | return filter {
19 | addedDict.updateValue(true, forKey: $0) == nil
20 | }
21 | }
22 |
23 | mutating func removeDuplicates() {
24 | self = self.removingDuplicates()
25 | }
26 | }
27 |
28 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "images" : [
3 | {
4 | "size" : "16x16",
5 | "idiom" : "mac",
6 | "filename" : "Icon_16x16.png",
7 | "scale" : "1x"
8 | },
9 | {
10 | "size" : "16x16",
11 | "idiom" : "mac",
12 | "filename" : "Icon_16x16@2x.png",
13 | "scale" : "2x"
14 | },
15 | {
16 | "size" : "32x32",
17 | "idiom" : "mac",
18 | "filename" : "Icon_32x32.png",
19 | "scale" : "1x"
20 | },
21 | {
22 | "size" : "32x32",
23 | "idiom" : "mac",
24 | "filename" : "Icon_32x32@2x.png",
25 | "scale" : "2x"
26 | },
27 | {
28 | "size" : "128x128",
29 | "idiom" : "mac",
30 | "filename" : "Icon_128x128.png",
31 | "scale" : "1x"
32 | },
33 | {
34 | "size" : "128x128",
35 | "idiom" : "mac",
36 | "filename" : "Icon_128x128@2x.png",
37 | "scale" : "2x"
38 | },
39 | {
40 | "size" : "256x256",
41 | "idiom" : "mac",
42 | "filename" : "Icon_256x256.png",
43 | "scale" : "1x"
44 | },
45 | {
46 | "size" : "256x256",
47 | "idiom" : "mac",
48 | "filename" : "Icon_256x256@2x.png",
49 | "scale" : "2x"
50 | },
51 | {
52 | "size" : "512x512",
53 | "idiom" : "mac",
54 | "filename" : "Icon_512x512.png",
55 | "scale" : "1x"
56 | },
57 | {
58 | "size" : "512x512",
59 | "idiom" : "mac",
60 | "filename" : "Icon_512x512@2x.png",
61 | "scale" : "2x"
62 | }
63 | ],
64 | "info" : {
65 | "version" : 1,
66 | "author" : "xcode"
67 | }
68 | }
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_128x128.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_128x128@2x.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_16x16.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_16x16@2x.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_256x256.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_256x256@2x.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_32x32.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_32x32@2x.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_512x512.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/ssamadgh/WWDCsrt/0fa30d0940420e3039b4cb4d5763eb351f7849d7/WWDCSubGetter/Assets.xcassets/AppIcon.appiconset/Icon_512x512@2x.png
--------------------------------------------------------------------------------
/WWDCSubGetter/Assets.xcassets/Contents.json:
--------------------------------------------------------------------------------
1 | {
2 | "info" : {
3 | "version" : 1,
4 | "author" : "xcode"
5 | }
6 | }
--------------------------------------------------------------------------------
/WWDCSubGetter/ConvertToSubtitleOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | VerifyVideoURLCondition.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/19/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | In this file we convert wwdc video links to `Subtitle` objects.
10 | */
11 |
12 | import Foundation
13 |
14 | /**
15 | This enum is for detecting input url type. We have Two types of input for video links.
16 | A single video link and tex file with a bunch of video links.
17 | */
18 | enum InputType {
19 | case textFile
20 | case videoLink
21 | }
22 |
23 | /// An `Operation` for validate the wwdc video links and convert them to `Subtitle`.
24 | final class ConvertToSubtitleOperation: Operation {
25 |
26 | let url: String
27 | let wwdc: WWDC
28 | let type: InputType
29 | var completion: (([Subtitle]) -> Void)!
30 |
31 | init(from url: String, wwdc: WWDC, type: InputType) {
32 | self.url = url
33 | self.wwdc = wwdc
34 | self.type = type
35 |
36 | super.init()
37 | self.name = "ConvertToSubtitleOperation"
38 | }
39 |
40 | convenience init(from url: String, wwdc: WWDC, type: InputType, completion: @escaping ([Subtitle]) -> Void) {
41 | self.init(from: url, wwdc: wwdc, type: type)
42 | self.completion = completion
43 |
44 | }
45 |
46 | override func execute() {
47 |
48 | /*
49 | We check validation of video links in each type seperately and put back `Subtitle`
50 | objects of validate links
51 | */
52 |
53 | switch self.type {
54 | case .videoLink:
55 |
56 | let pattern = ".m[op4][v4]"
57 | if url.contains(pattern) {
58 | let url = self.url.trimmingCharacters(in: .whitespacesAndNewlines)
59 | model.createSubtitle(with: url, wwdc: wwdc)
60 | }
61 |
62 | case .textFile:
63 | let array = convertTextFileToArray()
64 | for url in array {
65 | let url = url.trimmingCharacters(in: .whitespacesAndNewlines)
66 | model.createSubtitle(with: url, wwdc: wwdc)
67 | }
68 | }
69 |
70 | let subtitlesArray = model.allSubtitles()
71 | completion(subtitlesArray)
72 | model.clear()
73 | finish()
74 | }
75 |
76 | /**
77 | This method converts text file to an array of video links were inside of text file.
78 | it also checks if the link is realy blong to a video and filters true links.
79 | */
80 | func convertTextFileToArray() -> [String] {
81 |
82 | do {
83 |
84 | // This solution assumes you've got the file in your bundle
85 |
86 | let data = try String(contentsOfFile:url, encoding: String.Encoding.utf8)
87 | let pattern = ".m[op4][v4]"
88 | let videosURLArray = data.components(separatedBy: "\n").filter {$0.contains(pattern)}
89 |
90 | return videosURLArray
91 |
92 | } catch {
93 |
94 | // do something with Error
95 | print(error.localizedDescription)
96 | }
97 | return []
98 | }
99 |
100 | }
101 |
102 |
--------------------------------------------------------------------------------
/WWDCSubGetter/DownloadHtmlVideoPageOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GetHtmlVideoPageOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | final class DownloadHtmlVideoPageOperation: GroupOperation {
12 |
13 | //MARK: Properties
14 |
15 | let cacheFile: URL
16 |
17 | //MARK: Initializer
18 |
19 | /// -parameter cacheFile: The file `URL` to wich the earthquake feed will be downloaded.
20 | init(wwdcYear: WWDC, sessionNumber: String, cacheFile: URL) {
21 | self.cacheFile = cacheFile
22 | super.init(operations: [])
23 | name = "GetHtmlVideoPageOperation \(sessionNumber)"
24 |
25 | let url = URL(string: "https://developer.apple.com/videos/play/\(wwdcYear.stringValue)/" + sessionNumber + "/")!
26 |
27 | let task = URLSession.shared.downloadTask(with: url) { (url, response, error) in
28 | self.downloadFinished(url, response: response, error: error as NSError?)
29 | return()
30 | }
31 |
32 | let taskOperation = URLSessionTaskOperation(task: task)
33 |
34 | let reachabilityCondition = ReachabilityCondition(host: url)
35 | taskOperation.addCondition(reachabilityCondition)
36 |
37 | addOperation(taskOperation)
38 |
39 | }
40 |
41 | func downloadFinished(_ url: URL?, response: URLResponse?, error: NSError?) {
42 | if let localURL = url {
43 | do {
44 | /*
45 | If we already have a file at this location, just delete it.
46 | Also swallow the error, because we don't really care about it.
47 | */
48 | try FileManager.default.removeItem(at: cacheFile)
49 | }
50 | catch { }
51 |
52 | do {
53 | try FileManager.default.moveItem(at: localURL, to: cacheFile)
54 | }
55 | catch let error as NSError {
56 | aggregateError(error)
57 | }
58 | }
59 | else if let error = error {
60 | aggregateError(error)
61 | }
62 | else {
63 | // Do nothing, and the operation will automatically finish.
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/WWDCSubGetter/DownloadM3U8Operation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | DownloadAndMakeSrtOperation.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/19/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This file contains the code to download the m3u8 file.
10 | */
11 |
12 | import Foundation
13 |
14 | /**
15 | An operation to download m3u8 file for input subtitle.
16 | - note: A m3u8 file, is a file blong to each video link and contains
17 | some informations about video subtitle files which are in webvtt format
18 | and we dwonload them later.
19 |
20 | */
21 | final class DownloadM3U8Operation: GroupOperation {
22 | // MARK: Properties
23 |
24 | var subtitle: Subtitle
25 |
26 | /// - parameter subtitle: The subtitle which we want to download its m3u8 file.
27 | init(subtitle: Subtitle) {
28 |
29 | self.subtitle = subtitle
30 |
31 | super.init(operations: [])
32 | name = "Download M3U8"
33 |
34 | let task = URLSession.shared.downloadTask(with: subtitle.m3u8URL) { (url, response, error) in
35 | self.downloadFinished(url, response: response, error: error as NSError?)
36 | return()
37 | }
38 |
39 | let taskOperation = URLSessionTaskOperation(task: task)
40 |
41 | addOperation(taskOperation)
42 |
43 | }
44 |
45 | func downloadFinished(_ url: URL?, response: URLResponse?, error: NSError?) {
46 | if let localURL = url {
47 |
48 | do {
49 |
50 | /*
51 | We convert m3u8 file to an arry of `Webvtt` objects,
52 | which are the original subtitle files.
53 | We download and convert this webvtt files in a single srt file later.
54 | */
55 | try self.subtitle.updateWebvtts(with: localURL)
56 | model.update(self.subtitle)
57 | }
58 | catch let error as NSError {
59 | aggregateError(error)
60 | }
61 |
62 | }
63 | else if let error = error {
64 | aggregateError(error)
65 | }
66 | else {
67 | // Do nothing, and the operation will automatically finish.
68 | }
69 | }
70 |
71 | }
72 |
--------------------------------------------------------------------------------
/WWDCSubGetter/DownloadWebvttOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | DownloadWebvttOperation.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/19/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This file contains the code to download the webvtt file.
10 |
11 | */
12 |
13 | import Foundation
14 |
15 | /**
16 | An operation to download webvtt file.
17 | - note: Webvtt files, are bunch of files used for online videos subtitles.
18 | we download them and cnvert them to a single srt file.
19 | */
20 | final class DownloadWebvttOperation: GroupOperation {
21 |
22 | var subtitle: Subtitle
23 | var webvtt: Webvtt
24 |
25 | init(subtitle: Subtitle, webvtt: Webvtt) {
26 | self.subtitle = subtitle
27 | self.webvtt = webvtt
28 |
29 | super.init(operations: [])
30 | name = "Download Webvtt \(webvtt.number)"
31 |
32 | let task = URLSession.shared.downloadTask(with: subtitle.url(for: webvtt)) { (url, response, error) in
33 | if let error = error {
34 | print(" the operation \(self.name!) of \(subtitle.videoURL) download task faced error : \(error)")
35 | }
36 | self.downloadFinished(url, response: response, error: error as NSError?)
37 | return()
38 | }
39 |
40 | let taskOperation = URLSessionTaskOperation(task: task)
41 |
42 | addOperation(taskOperation)
43 |
44 | }
45 |
46 | func downloadFinished(_ url: URL?, response: URLResponse?, error: NSError?) {
47 | if let localURL = url {
48 |
49 | do {
50 | /*
51 | After download finished, we get the content of webvtt file
52 | and convert it to a standard srt subtitle content.
53 | */
54 | var content = try String(contentsOf: localURL, encoding: String.Encoding.utf8)
55 | content = content.replace("WEBVTT", with: "").replace(">", with: ">").replace("<", with: "<").replace("&", with: "&")
56 |
57 | let pattern = "\nX-TIMESTAMP-MAP[\\w+:,=.//d+]+\n\n"
58 | content = content.replace(matches: pattern, with: "")!
59 |
60 | let newContent = self.convertWebvttToSrt(webvtt: content)
61 | webvtt.content = newContent
62 |
63 | model.add(self.webvtt, to: self.subtitle)
64 |
65 | }
66 | catch let error as NSError {
67 | aggregateError(error)
68 | }
69 |
70 | }
71 | else if let error = error {
72 | aggregateError(error)
73 | }
74 | else {
75 | // Do nothing, and the operation will automatically finish.
76 | }
77 |
78 | }
79 |
80 | ///This method converts webvtt standard content to srt standard content
81 | func convertWebvttToSrt(webvtt: String) -> String {
82 | let timeLinePattern = "([0-9:.]+)\\.(\\d+ --> [0-9:.]+)\\.(\\d+) (\\w+:\\w+)\\%?"
83 | var firstLines:[String] = []
84 | var newLines:[String] = []
85 | var newSrt = webvtt
86 |
87 | firstLines = webvtt.list(matches: timeLinePattern) ?? []
88 |
89 | if !firstLines.isEmpty {
90 | for line in firstLines {
91 |
92 | let groups = line.captureGroups(with: timeLinePattern)!
93 |
94 | let newLine: String = groups[1] + "," + groups[2] + "," + groups[3]
95 | newLines.append(newLine)
96 | }
97 |
98 | for i in 0...firstLines.count-1 {
99 | newSrt = newSrt.replace(firstLines[i], with: newLines[i])
100 | }
101 | }
102 | return newSrt
103 | }
104 |
105 |
106 | }
107 |
--------------------------------------------------------------------------------
/WWDCSubGetter/ExportLinksOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ExportLinksOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/7/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | /// A subclass of Operation which executes `exportSrtFile()` method for input subtitle.
12 | final class ExportLinksOperation: Operation {
13 |
14 | let wwdcYear: WWDC
15 | let copyToUserDestinationURL: Bool
16 |
17 | /// - parameter subtitle: subtitle we want execute it's `exportSrtFile()` method.
18 | init(wwdcYear: WWDC, copyToUserDestinationURL: Bool) {
19 | self.wwdcYear = wwdcYear
20 | self.copyToUserDestinationURL = copyToUserDestinationURL
21 |
22 | super.init()
23 | name = "ExportLinksOperation"
24 | }
25 |
26 |
27 | override func execute() {
28 |
29 | let fileManager = FileManager.default
30 |
31 | //userDestinationURL
32 | let userDestinationURL = model.destinationURL!
33 |
34 | do {
35 |
36 | if copyToUserDestinationURL {
37 | if !fileManager.fileExists(atPath: userDestinationURL.path) {
38 | try fileManager.createDirectory(at: userDestinationURL, withIntermediateDirectories: true, attributes: nil)
39 | }
40 | }
41 |
42 | if !linksModel.titles.isEmpty, !copyToUserDestinationURL {
43 | let titlesURL = linksModel.titlesCacheURLFor(wwdcYear)
44 | let text = linksModel.titles.removingDuplicates().sorted().joined(separator: "\n")
45 | try text.write(to: titlesURL, atomically: false, encoding: String.Encoding.utf8)
46 | }
47 |
48 | if !linksModel.hdVideosLinks.isEmpty {
49 | let text = linksModel.hdVideosLinks.removingDuplicates().sorted().joined(separator: "\n")
50 | if copyToUserDestinationURL {
51 | let userHdVideoLinksURL = linksModel.userHdVideoLinksURLFor(wwdcYear)
52 | try text.write(to: userHdVideoLinksURL, atomically: false, encoding: String.Encoding.utf8)
53 | }
54 | else {
55 | let hdVideoLinksURL = linksModel.hdVideoCacheURLFor(wwdcYear)
56 | try text.write(to: hdVideoLinksURL, atomically: false, encoding: String.Encoding.utf8)
57 | }
58 | }
59 |
60 | if !linksModel.sdVideosLinks.isEmpty {
61 | let text = linksModel.sdVideosLinks.removingDuplicates().sorted().joined(separator: "\n")
62 | if copyToUserDestinationURL {
63 | let userSdVideoLinksURL = linksModel.userSdVideoLinksURLFor(wwdcYear)
64 | try text.write(to: userSdVideoLinksURL, atomically: false, encoding: String.Encoding.utf8)
65 | }
66 | else {
67 | let sdVideoLinksURL = linksModel.sdVideoCacheURLFor(wwdcYear)
68 | try text.write(to: sdVideoLinksURL, atomically: false, encoding: String.Encoding.utf8)
69 | }
70 | }
71 |
72 | if !linksModel.pdfLinks.isEmpty {
73 | let text = linksModel.pdfLinks.removingDuplicates().sorted().joined(separator: "\n")
74 | if copyToUserDestinationURL {
75 | let userPdfLinksURL = linksModel.userPdfLinksURLFor(wwdcYear)
76 | try text.write(to: userPdfLinksURL, atomically: false, encoding: String.Encoding.utf8)
77 | }
78 | else {
79 | let pdfLinksURL = linksModel.pdfLinksCacheURLFor(wwdcYear)
80 | try text.write(to: pdfLinksURL, atomically: false, encoding: String.Encoding.utf8)
81 | }
82 | }
83 |
84 | if !linksModel.sampleCodesLinks.isEmpty {
85 | let text = linksModel.sampleCodesLinks.removingDuplicates().sorted().joined(separator: "\n")
86 | if copyToUserDestinationURL {
87 | let userSampleCodesLinksURL = linksModel.userSampleCodesLinksURLFor(wwdcYear)
88 | try text.write(to: userSampleCodesLinksURL, atomically: false, encoding: String.Encoding.utf8)
89 | }
90 | else {
91 | let sampleCodesLinksURL = linksModel.sampleCodesLinksCacheURLFor(wwdcYear)
92 | try text.write(to: sampleCodesLinksURL, atomically: false, encoding: String.Encoding.utf8)
93 |
94 | }
95 |
96 | }
97 |
98 | }
99 | catch {/* error handling here */
100 | print(error.localizedDescription)
101 | }
102 |
103 | linksModel.clear()
104 | finish()
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/WWDCSubGetter/ExportSrtFileOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | ExportSrtFilesOperation.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/20/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract: This files export srt files
9 | */
10 |
11 | import Foundation
12 |
13 | /// A subclass of Operation which executes `exportSrtFile()` method for input subtitle.
14 | final class ExportSrtFileOperation: Operation {
15 |
16 | let subtitle: Subtitle
17 |
18 | /// - parameter subtitle: subtitle we want execute it's `exportSrtFile()` method.
19 | init(subtitle: Subtitle) {
20 | self.subtitle = subtitle
21 | super.init()
22 | name = "Export Srt File"
23 | }
24 |
25 |
26 | override func execute() {
27 |
28 | let subtitle = model.subtitle(for: self.subtitle.id)!
29 |
30 | subtitle.exportSrtFile()
31 | finish()
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/WWDCSubGetter/FetchHtmlVideoPagesOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrseSessionsListOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class FetchHtmlVideoPagesOperation: GroupOperation {
13 |
14 | //MARK: Properties
15 |
16 | // let sessionListURL: URL
17 | let wwdcYear: WWDC
18 | let types: [SessionDataTypes]
19 | let sessionNumber: String?
20 | //MARK: Initializer
21 |
22 | init(for types: [SessionDataTypes], wwdcYear: WWDC, sessionNumber: String?) {
23 |
24 | self.wwdcYear = wwdcYear
25 | self.types = types
26 | self.sessionNumber = sessionNumber
27 |
28 | super.init(operations: [])
29 | self.name = "ParseSessionsListOperation"
30 | self.limitMaxConcurrentOperations(to: 3)
31 | }
32 |
33 | override func execute() {
34 | let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
35 | let downloadLinksCacheFolder = cachesFolder.appendingPathComponent("com.samad.WWDC.srt/\(wwdcYear.stringValue)/", isDirectory: true)
36 |
37 | var operations: [Foundation.Operation] = []
38 |
39 | do {
40 |
41 | var sessionNumbers: [String] = []
42 |
43 | if self.sessionNumber == nil {
44 |
45 | let titleURL = linksModel.titlesCacheURLFor(wwdcYear)
46 |
47 | if FileManager.default.fileExists(atPath: titleURL.path) {
48 |
49 | let data = try String(contentsOfFile:titleURL.path, encoding: String.Encoding.utf8)
50 | let sessionsListArray = data.components(separatedBy: "\n")
51 | sessionNumbers = sessionsListArray.compactMap { String($0.split(separator: " ").first!) }
52 |
53 | }
54 |
55 | }
56 | else {
57 | sessionNumbers = [self.sessionNumber!]
58 | }
59 |
60 | for sessionNumber in sessionNumbers {
61 |
62 | let htmlURL = downloadLinksCacheFolder.appendingPathComponent("\(sessionNumber).html")
63 |
64 | let parseHtmlOperation = ParseHtmlVideoPageOperation(for: types, sessionNumber: sessionNumber, cacheFile: htmlURL)
65 |
66 | if self.wwdcYear == lastWWDC {
67 | let getHtmlOperation = DownloadHtmlVideoPageOperation(wwdcYear: wwdcYear, sessionNumber: sessionNumber, cacheFile: htmlURL)
68 | operations.append(getHtmlOperation)
69 |
70 |
71 | parseHtmlOperation.addDependency(getHtmlOperation)
72 | }
73 | else if !FileManager.default.fileExists(atPath: htmlURL.path) {
74 |
75 | let getHtmlOperation = DownloadHtmlVideoPageOperation(wwdcYear: wwdcYear, sessionNumber: sessionNumber, cacheFile: htmlURL)
76 | operations.append(getHtmlOperation)
77 |
78 |
79 | parseHtmlOperation.addDependency(getHtmlOperation)
80 | }
81 |
82 | operations.append(parseHtmlOperation)
83 | }
84 |
85 | } catch {
86 |
87 | print(error)
88 | }
89 |
90 | addOperations(operations)
91 |
92 | super.execute()
93 |
94 | }
95 |
96 | }
97 |
--------------------------------------------------------------------------------
/WWDCSubGetter/FetchSubtitilesOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | FetchSubtitilesOperation.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/19/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | In this file we download m3u8 file for each subtitle and fetch webvtt files
10 | and a th the end we export srt files.
11 |
12 | */
13 |
14 | import Foundation
15 |
16 | /// A composite operation to download m3u8 files, fetching webvtt files and exporting srt file for each `Subtitle` concurrently.
17 | final class FetchSubtitilesOperation: GroupOperation {
18 |
19 |
20 | init() {
21 | super.init(operations: [])
22 | name = "FetchSubtitilesOp"
23 |
24 | // We limit maximum of concurrent operations to avoid of creating too many operations for a bunch of video links
25 | self.limitMaxConcurrentOperations(to: 3)
26 |
27 | for subtitle in model.allSubtitles() {
28 |
29 | let downloadOperation = DownloadM3U8Operation(subtitle: subtitle)
30 | let fetchWebvttsOperation = FetchWebvttsOperation(subtitle: subtitle)
31 | let exportSrtFileOperation = ExportSrtFileOperation(subtitle: subtitle)
32 |
33 | exportSrtFileOperation.addDependency(fetchWebvttsOperation)
34 | fetchWebvttsOperation.addDependency(downloadOperation)
35 |
36 | addOperations([downloadOperation, fetchWebvttsOperation, exportSrtFileOperation])
37 |
38 | }
39 |
40 |
41 | }
42 |
43 | }
44 |
--------------------------------------------------------------------------------
/WWDCSubGetter/FetchWebvttsOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | DownloadWebvttsOperation.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/19/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This file fetches webvtt files related to each subtitle
10 | */
11 |
12 | import Foundation
13 |
14 | ///A compose Operation to fetch webvtts files for input subtitle concurrently.
15 | final class FetchWebvttsOperation: GroupOperation {
16 |
17 | var subtitle: Subtitle
18 |
19 | /// - parameter subtitle: The subtitle which we want to fetch its webvtt files.
20 | init(subtitle: Subtitle) {
21 | self.subtitle = subtitle
22 |
23 | super.init(operations: [])
24 | name = "Fetch Webvtts"
25 |
26 | // We limit maximum of concurrent operations to avoid of creating too many operations for a bunch of Webvtt links
27 | self.limitMaxConcurrentOperations(to: 3)
28 |
29 | }
30 |
31 | /*
32 | We create child operations and add them to queue in `execute()` method instead of in `init(...)`.
33 | its because when we initialize this operation in `FetchSubtitlesOperation`,
34 | we don't have webvtts in subtitles `webvtts` array, so we must wait to
35 | download subtitle's m3u8 file and making webvtts, and then create webvtts
36 | download operations.
37 | */
38 |
39 | override func execute() {
40 |
41 | /*
42 | Because Subtite is a struct and is a value type, we need to invoke it in `execute()` method
43 | to have an updated version of it
44 | */
45 | let sub = model.subtitle(for: self.subtitle.id)!
46 | let webvttArray = sub.webvtts
47 |
48 | for webvtt in webvttArray {
49 |
50 | let downloadOperation = DownloadWebvttOperation(subtitle: sub, webvtt: webvtt)
51 | addOperation(downloadOperation)
52 | }
53 |
54 | // We clear subtitle webvtts array, because after downloading webvtts, we fill this array with new webvtts.
55 | model.clearWebvttArray(of: sub)
56 |
57 | super.execute()
58 | }
59 |
60 | }
61 |
--------------------------------------------------------------------------------
/WWDCSubGetter/GetHtmlSessionsListOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GetHtmlSessionsListOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class GetHtmlSessionsListOperation: GroupOperation {
13 |
14 | //MARK: Properties
15 |
16 | let cacheFile: URL
17 |
18 | //MARK: Initializer
19 |
20 | /// -parameter cacheFile: The file `URL` to wich the earthquake feed will be downloaded.
21 | init(wwdcYear: WWDC, cacheFile: URL) {
22 | self.cacheFile = cacheFile
23 | super.init(operations: [])
24 | name = "GetHtmlSessionsListOperation"
25 |
26 | let url = URL(string: "https://developer.apple.com/videos/\(wwdcYear.stringValue)/")!
27 |
28 | let task = URLSession.shared.downloadTask(with: url) { (url, response, error) in
29 | self.downloadFinished(url, response: response, error: error as NSError?)
30 | return()
31 | }
32 |
33 | let taskOperation = URLSessionTaskOperation(task: task)
34 |
35 | let reachabilityCondition = ReachabilityCondition(host: url)
36 | taskOperation.addCondition(reachabilityCondition)
37 |
38 | addOperation(taskOperation)
39 | }
40 |
41 | func downloadFinished(_ url: URL?, response: URLResponse?, error: NSError?) {
42 | if let localURL = url {
43 | do {
44 | /*
45 | If we already have a file at this location, just delete it.
46 | Also swallow the error, because we don't really care about it.
47 | */
48 | try FileManager.default.removeItem(at: cacheFile)
49 | }
50 | catch { }
51 |
52 | do {
53 | try FileManager.default.moveItem(at: localURL, to: cacheFile)
54 | }
55 | catch let error as NSError {
56 | aggregateError(error)
57 | }
58 | }
59 | else if let error = error {
60 | aggregateError(error)
61 | }
62 | else {
63 | // Do nothing, and the operation will automatically finish.
64 | }
65 | }
66 |
67 | }
68 |
--------------------------------------------------------------------------------
/WWDCSubGetter/GetLinksOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GetLinksOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | final class GetLinksOperation: GroupOperation {
12 |
13 | var wwdcYear: WWDC
14 |
15 | var fetchHtmlVideoPagesOperation: FetchHtmlVideoPagesOperation
16 | var exportLinksOperation: ExportLinksOperation!
17 |
18 | init(for types: [SessionDataTypes], wwdcYear: WWDC, sessionNumber: String? = nil, copyToUserDestinationURL: Bool, completionHandler: @escaping () -> Void) {
19 | self.wwdcYear = wwdcYear
20 |
21 | //com.samad.WWDC.srt
22 |
23 | self.fetchHtmlVideoPagesOperation = FetchHtmlVideoPagesOperation(for: types, wwdcYear: wwdcYear, sessionNumber: sessionNumber)
24 |
25 | self.exportLinksOperation = ExportLinksOperation(wwdcYear: wwdcYear, copyToUserDestinationURL: copyToUserDestinationURL)
26 |
27 | let finishOperation = Foundation.BlockOperation(block: completionHandler)
28 |
29 | self.exportLinksOperation.addDependency(self.fetchHtmlVideoPagesOperation)
30 | finishOperation.addDependency(self.exportLinksOperation!)
31 |
32 |
33 | let operations = [self.fetchHtmlVideoPagesOperation, self.exportLinksOperation, finishOperation].filter { $0 != nil } as! [Foundation.Operation]
34 |
35 |
36 | super.init(operations: operations)
37 |
38 | self.name = "GetLinksOperation"
39 | }
40 |
41 | }
42 |
--------------------------------------------------------------------------------
/WWDCSubGetter/GetSessionsListOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // GetLinksOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Cocoa
10 |
11 | final class GetSessionsListOperation: GroupOperation {
12 |
13 | var wwdcYear: WWDC
14 | var getSessionsListOperation: GetHtmlSessionsListOperation?
15 | var parseSessionsListOperation: ParseSessionsListOperation
16 |
17 | init(for wwdcYear: WWDC, copyToUserDestinationURL: Bool, completionHandler: @escaping () -> Void) {
18 | self.wwdcYear = wwdcYear
19 |
20 | //com.samad.WWDC.srt
21 |
22 | let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
23 |
24 | let downloadLinksCacheFolder = cachesFolder.appendingPathComponent("com.samad.WWDC.srt/\(wwdcYear.stringValue)/", isDirectory: true)
25 |
26 | let fileManager = FileManager.default
27 |
28 | if !fileManager.fileExists(atPath: downloadLinksCacheFolder.path) {
29 |
30 | try! FileManager.default.createDirectory(atPath: downloadLinksCacheFolder.path, withIntermediateDirectories: true, attributes: nil)
31 | }
32 |
33 | let sessionListURL = downloadLinksCacheFolder.appendingPathComponent("sessionList.html")
34 |
35 | self.getSessionsListOperation = GetHtmlSessionsListOperation(wwdcYear: wwdcYear, cacheFile: sessionListURL)
36 |
37 | self.parseSessionsListOperation = ParseSessionsListOperation(for: wwdcYear, cacheFile: sessionListURL)
38 |
39 |
40 | let finishOperation = Foundation.BlockOperation(block: completionHandler)
41 |
42 | if self.getSessionsListOperation != nil {
43 | self.parseSessionsListOperation.addDependency(self.getSessionsListOperation!)
44 | }
45 |
46 | finishOperation.addDependency(self.parseSessionsListOperation)
47 |
48 |
49 | let operations = [self.getSessionsListOperation, self.parseSessionsListOperation, finishOperation].compactMap { $0 }
50 |
51 | super.init(operations: operations)
52 |
53 | self.name = "GetLinksOperation"
54 | }
55 |
56 | }
57 |
--------------------------------------------------------------------------------
/WWDCSubGetter/GetSubtitlesOperation.swift:
--------------------------------------------------------------------------------
1 |
2 | /*
3 | GetSubtitleOperation.swift
4 | WWDC.srt
5 |
6 | Created by Seyed Samad Gholamzadeh on 7/19/1396 AP.
7 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
8 |
9 | Abstract:
10 | This file sets up the operations to fetch subtitles from internet. It will also decide to display an error message, if appropriate.
11 | */
12 |
13 | import Cocoa
14 |
15 | /// A composite operation for fetching subtitles and clearing model.
16 | final class GetSubtitlesOperation: GroupOperation {
17 |
18 | /**
19 | - parameter completionHandler: The handler to call after fetching and
20 | clearing model are complete. This handler will be
21 | invoked on an arbitrary queue.
22 | */
23 | init(completionHandler: @escaping () -> Void) {
24 |
25 | super.init(operations: [])
26 | name = "Get Subtitles"
27 |
28 | /*
29 | This operation is made of three child operations:
30 | 1. The operation to fetch subtitle.
31 | 2. The operation to clear model after fetching ends.
32 | 3. The operation to invoke the completion handler.
33 | */
34 |
35 | let fetchSubtitlesOperation = FetchSubtitilesOperation()
36 | let sampleURL = URL(string: "https://www.google.com")!
37 | let reachabilityCondition = ReachabilityCondition(host: sampleURL)
38 | fetchSubtitlesOperation.addCondition(reachabilityCondition)
39 |
40 | let clearModelOperation = BlockOperation { (block) in
41 | model.clear()
42 | block()
43 | }
44 |
45 | let finishOperation = Foundation.BlockOperation(block: completionHandler)
46 |
47 |
48 | // These operations must be executed in order
49 | clearModelOperation.addDependency(fetchSubtitlesOperation)
50 | finishOperation.addDependency(fetchSubtitlesOperation)
51 |
52 | addOperations([fetchSubtitlesOperation, clearModelOperation, finishOperation])
53 | }
54 |
55 | override func operationDidFinish(_ operation: Foundation.Operation, withErrors errors: [NSError]) {
56 | guard !errors.isEmpty else { return }
57 | self.produceAlert(errors.first!)
58 | }
59 |
60 | fileprivate func produceAlert(_ error: NSError) {
61 | /*
62 | Here we represent error in viewController.
63 | We only checks if the error is about reachability condition,
64 | if not it probably is about problem to find approperiate subtitle
65 | or failing to downlading it, by the way we alert user
66 | downlad of some subtitles failed.
67 | */
68 | var errorDescription = ""
69 | if (error.userInfo[OperationConditionKey] as? String) == ReachabilityCondition.name {
70 | errorDescription = "Error: Can not Connect to internet. Please check your connection!"
71 | }
72 | else {
73 | errorDescription = "Error: Failed to download some subtitles."
74 | }
75 | if let presentationContext = NSApplication.shared.keyWindow?.contentViewController as? MainViewController {
76 | presentationContext.endDownloadingStatus(withError: errorDescription)
77 | }
78 | print(error)
79 | }
80 |
81 |
82 | }
83 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Info.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | CFBundleDevelopmentRegion
6 | en
7 | CFBundleExecutable
8 | $(EXECUTABLE_NAME)
9 | CFBundleIconFile
10 |
11 | CFBundleIdentifier
12 | $(PRODUCT_BUNDLE_IDENTIFIER)
13 | CFBundleInfoDictionaryVersion
14 | 6.0
15 | CFBundleName
16 | $(PRODUCT_NAME)
17 | CFBundlePackageType
18 | APPL
19 | CFBundleShortVersionString
20 | $(MARKETING_VERSION)
21 | CFBundleVersion
22 | 1
23 | LSApplicationCategoryType
24 | public.app-category.utilities
25 | LSMinimumSystemVersion
26 | $(MACOSX_DEPLOYMENT_TARGET)
27 | NSAppTransportSecurity
28 |
29 | NSAllowsArbitraryLoads
30 |
31 |
32 | NSHumanReadableCopyright
33 | Copyright © 2023 AP Seyed Samad Gholamzadeh. All rights reserved.
34 | NSMainStoryboardFile
35 | Main
36 | NSPrincipalClass
37 | NSApplication
38 |
39 |
40 |
--------------------------------------------------------------------------------
/WWDCSubGetter/LinksModel.swift:
--------------------------------------------------------------------------------
1 | //
2 | // LinksModel.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class SafeLinksModel {
12 |
13 | var queue = DispatchQueue(label: "LinkModel_Thread_Safe_Queue")
14 |
15 | private var _linksModel = LinksModel()
16 |
17 |
18 | var linksModel: LinksModel {
19 | get {
20 | queue.sync {
21 | return self._linksModel
22 | }
23 | }
24 |
25 | set {
26 | queue.async {
27 | self._linksModel = newValue
28 | }
29 | }
30 | }
31 |
32 | }
33 |
34 | fileprivate var safeLinksModel = SafeLinksModel()
35 | var linksModel: LinksModel {
36 | get {
37 | return safeLinksModel.linksModel
38 | }
39 |
40 | set {
41 | safeLinksModel.linksModel = newValue
42 | }
43 | }
44 |
45 | struct LinksModel {
46 | var titles: [String] = []
47 | var hdVideosLinks: [String] = []
48 | var sdVideosLinks: [String] = []
49 | var pdfLinks: [String] = []
50 | var sampleCodesLinks: [String] = []
51 | let cachesFolder = try! FileManager.default.url(for: .cachesDirectory, in: .userDomainMask, appropriateFor: nil, create: true)
52 |
53 |
54 | mutating func clear() {
55 | self.hdVideosLinks.removeAll()
56 | self.sdVideosLinks.removeAll()
57 | self.pdfLinks.removeAll()
58 | self.sampleCodesLinks.removeAll()
59 | self.titles.removeAll()
60 | }
61 |
62 | var cacheDestinationURL: URL {
63 | let destinationURL = cachesFolder.appendingPathComponent("com.samad.WWDC.srt", isDirectory: true)
64 | return destinationURL
65 | }
66 |
67 | func cacheDestinationURFor(_ wwdcYear: WWDC) -> URL {
68 | let destinationURL = cachesFolder.appendingPathComponent("com.samad.WWDC.srt/\(wwdcYear.stringValue)/", isDirectory: true)
69 | return destinationURL
70 | }
71 |
72 |
73 | func titlesCacheURLFor(_ wwdcYear: WWDC) -> URL {
74 | let destinationURL = cacheDestinationURFor(wwdcYear)
75 | let titlesURL = destinationURL.appendingPathComponent("\(wwdcYear.stringValue)_sessions_titles.txt")
76 |
77 | return titlesURL
78 | }
79 |
80 |
81 | func hdVideoLinksFileNameFor(_ wwdcYear: WWDC) -> String {
82 | return "\(wwdcYear.stringValue)_hd_video_links.txt"
83 | }
84 |
85 | func hdVideoCacheURLFor(_ wwdcYear: WWDC) -> URL {
86 | let destinationURL = cacheDestinationURFor(wwdcYear)
87 | let titlesURL = destinationURL.appendingPathComponent(hdVideoLinksFileNameFor(wwdcYear))
88 |
89 | return titlesURL
90 | }
91 |
92 | func userHdVideoLinksURLFor(_ wwdcYear: WWDC) -> URL {
93 | let userDestinationURL = model.destinationURL!
94 | let userHdVideoLinksURL = userDestinationURL.appendingPathComponent(hdVideoLinksFileNameFor(wwdcYear))
95 | return userHdVideoLinksURL
96 | }
97 |
98 | func sdVideoLinksFileNameFor(_ wwdcYear: WWDC) -> String {
99 | return "\(wwdcYear.stringValue)_sd_video_links.txt"
100 | }
101 |
102 | func sdVideoCacheURLFor(_ wwdcYear: WWDC) -> URL {
103 | let destinationURL = cacheDestinationURFor(wwdcYear)
104 | let titlesURL = destinationURL.appendingPathComponent(sdVideoLinksFileNameFor(wwdcYear))
105 |
106 | return titlesURL
107 | }
108 |
109 | func userSdVideoLinksURLFor(_ wwdcYear: WWDC) -> URL {
110 | let userDestinationURL = model.destinationURL!
111 | let userSdVideoLinksURL = userDestinationURL.appendingPathComponent(sdVideoLinksFileNameFor(wwdcYear))
112 | return userSdVideoLinksURL
113 | }
114 |
115 | func pdfLinksFileNameFor(_ wwdcYear: WWDC) -> String {
116 | return "\(wwdcYear.stringValue)_pdf_links.txt"
117 | }
118 |
119 | func pdfLinksCacheURLFor(_ wwdcYear: WWDC) -> URL {
120 | let destinationURL = cacheDestinationURFor(wwdcYear)
121 | let titlesURL = destinationURL.appendingPathComponent(pdfLinksFileNameFor(wwdcYear))
122 |
123 | return titlesURL
124 | }
125 |
126 | func userPdfLinksURLFor(_ wwdcYear: WWDC) -> URL {
127 | let userDestinationURL = model.destinationURL!
128 | let userPdfLinksURL = userDestinationURL.appendingPathComponent(pdfLinksFileNameFor(wwdcYear))
129 | return userPdfLinksURL
130 | }
131 |
132 | func sampleCodesLinksFileNameFor(_ wwdcYear: WWDC) -> String {
133 | return "\(wwdcYear.stringValue)_sample_codes_links.txt"
134 | }
135 |
136 | func sampleCodesLinksCacheURLFor(_ wwdcYear: WWDC) -> URL {
137 | let destinationURL = cacheDestinationURFor(wwdcYear)
138 | let titlesURL = destinationURL.appendingPathComponent(sampleCodesLinksFileNameFor(wwdcYear))
139 |
140 | return titlesURL
141 | }
142 |
143 | func userSampleCodesLinksURLFor(_ wwdcYear: WWDC) -> URL {
144 | let userDestinationURL = model.destinationURL!
145 | let userSampleCodesLinksURL = userDestinationURL.appendingPathComponent(sampleCodesLinksFileNameFor(wwdcYear))
146 | return userSampleCodesLinksURL
147 | }
148 |
149 |
150 |
151 | }
152 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Model.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Model.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 6/25/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This file contains struct Model which manages subtitles.
10 | */
11 |
12 | import Foundation
13 |
14 | typealias ID = Int
15 |
16 | /**
17 | We created a global instance of `Model`, thus we can manage our subtitles easily
18 | in every class and struct.
19 | */
20 |
21 | class SafeModel {
22 |
23 | var queue = DispatchQueue(label: "Model_Thread_Safe_Queue")
24 |
25 | private var _model = Model()
26 |
27 |
28 | var model: Model {
29 | get {
30 | queue.sync {
31 | return self._model
32 | }
33 | }
34 |
35 | set {
36 | queue.async {
37 | self._model = newValue
38 | }
39 | }
40 | }
41 |
42 | }
43 |
44 | fileprivate var safeModel = SafeModel()
45 |
46 | var model: Model {
47 | get {
48 | return safeModel.model
49 | }
50 |
51 | set {
52 | safeModel.model = newValue
53 | }
54 | }
55 |
56 |
57 |
58 | struct Model {
59 |
60 | /// A dictionay that saves subtitles with their id
61 | private var videosSub: [Int: Subtitle] = [:]
62 |
63 | /// The destination Local URL Which subtitles exported there
64 | var destinationURL: URL?
65 |
66 | /// A Boolean value that indicates whether the model is empty.
67 | var isEmpty: Bool {
68 | return self.videosSub.isEmpty
69 | }
70 |
71 | /**
72 | This method create a subtitle object with given `videoURL`
73 | and if it wasn't nil, adds it to the model.
74 | */
75 | mutating func createSubtitle(with videoURL: String, wwdc: WWDC) {
76 | if let subtitle = Subtitle(videoURL: videoURL, wwdc: wwdc) {
77 | self.videosSub[subtitle.id] = subtitle
78 | }
79 | }
80 |
81 | /// This method updates model subtitles, with the given subtitles.
82 | mutating func update(_ subtitles: [Subtitle]) {
83 | for subtitle in subtitles {
84 | self.videosSub[subtitle.id] = subtitle
85 | }
86 | }
87 |
88 | /// This method updates model subtitle, with the given subtitle.
89 | mutating func update(_ subtitle: Subtitle) {
90 | self.update([subtitle])
91 | }
92 |
93 | /// This method returns subtitle that saved in the model with the given id
94 | func subtitle(for id: ID) -> Subtitle? {
95 | return self.videosSub[id]
96 | }
97 |
98 | /// This method returns all subtitles saved in the model.
99 | func allSubtitles() -> [Subtitle] {
100 | return self.videosSub.values.sorted()
101 | }
102 |
103 | /// This method adds given webvtt to the specified model subtitle
104 | mutating func add(_ webvtt: Webvtt, to subtitle: Subtitle) {
105 | self.videosSub[subtitle.id]?.appendWebvtt(webvtt)
106 | }
107 |
108 | /// This method clears webvttArray of the specified model subtitle
109 | mutating func clearWebvttArray(of subtitle: Subtitle) {
110 | self.videosSub[subtitle.id]?.clearWebvtts()
111 | }
112 |
113 | /// This method clears all subtitles saved in the model.
114 | mutating func clear() {
115 | self.videosSub.removeAll()
116 | }
117 |
118 | }
119 |
120 |
121 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Conditions/MutuallyExclusive.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file shows an example of implementing the OperationCondition protocol.
7 | */
8 |
9 | import Foundation
10 |
11 | /// A generic condition for describing kinds of operations that may not execute concurrently.
12 | struct MutuallyExclusive: OperationCondition {
13 |
14 |
15 | static var name: String {
16 | return "MutuallyExclusive<\(T.self)>"
17 | }
18 |
19 | static var isMutuallyExclusive: Bool {
20 | return true
21 | }
22 |
23 | init() { }
24 |
25 | func dependencyForOperation(_ operation: Operation) -> Foundation.Operation? {
26 | return nil
27 | }
28 |
29 | func evaluateForOperation(_ operation: Operation, completion: @escaping (OperationConditionResult) -> Void) {
30 | completion(.satisfied)
31 | }
32 | }
33 |
34 | /**
35 | The purpose of this enum is to simply provide a non-constructible
36 | type to be used with `MutuallyExclusive`.
37 | */
38 | enum Alert { }
39 |
40 | /// A condition describing that the targeted operation may present an alert.
41 | typealias AlertPresentation = MutuallyExclusive
42 |
43 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Conditions/OperationCondition.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the fundamental logic relating to Operation conditions.
7 | */
8 |
9 | import Foundation
10 |
11 | let OperationConditionKey = "OperationCondition"
12 |
13 | /**
14 | A protocol for defining conditions that must be satisfied in order for an
15 | operation to begin execution.
16 | */
17 | protocol OperationCondition {
18 | /**
19 | The name of the condition. This is used in userInfo dictionaries of `.ConditionFailed`
20 | errors as the value of the `OperationConditionKey` key.
21 | */
22 | static var name: String { get }
23 |
24 | /**
25 | Specifies whether multiple instances of the conditionalized operation may
26 | be executing simultaneously.
27 | */
28 | static var isMutuallyExclusive: Bool { get }
29 |
30 | /**
31 | Some conditions may have the ability to satisfy the condition if another
32 | operation is executed first. Use this method to return an operation that
33 | (for example) asks for permission to perform the operation
34 |
35 | - parameter operation: The `Operation` to which the Condition has been added.
36 | - returns: An `NSOperation`, if a dependency should be automatically added. Otherwise, `nil`.
37 | - note: Only a single operation may be returned as a dependency. If you
38 | find that you need to return multiple operations, then you should be
39 | expressing that as multiple conditions. Alternatively, you could return
40 | a single `GroupOperation` that executes multiple operations internally.
41 | */
42 | func dependencyForOperation(_ operation: Operation) -> Foundation.Operation?
43 |
44 | /// Evaluate the condition, to see if it has been satisfied or not.
45 | func evaluateForOperation(_ operation: Operation, completion: @escaping (OperationConditionResult) -> Void)
46 | }
47 |
48 | /**
49 | An enum to indicate whether an `OperationCondition` was satisfied, or if it
50 | failed with an error.
51 | */
52 | enum OperationConditionResult: Equatable {
53 | case satisfied
54 | case failed(NSError)
55 |
56 | var error: NSError? {
57 | if case .failed(let error) = self {
58 | return error
59 | }
60 |
61 | return nil
62 | }
63 | }
64 |
65 | func ==(lhs: OperationConditionResult, rhs: OperationConditionResult) -> Bool {
66 | switch (lhs, rhs) {
67 | case (.satisfied, .satisfied):
68 | return true
69 | case (.failed(let lError), .failed(let rError)) where lError == rError:
70 | return true
71 | default:
72 | return false
73 | }
74 | }
75 |
76 | // MARK: Evaluate Conditions
77 |
78 | struct OperationConditionEvaluator {
79 | static func evaluate(_ conditions: [OperationCondition], operation: Operation, completion: @escaping ([NSError]) -> Void) {
80 | // Check conditions.
81 | let conditionGroup = DispatchGroup()
82 |
83 | var results = [OperationConditionResult?](repeating: nil, count: conditions.count)
84 |
85 | // Ask each condition to evaluate and store its result in the "results" array.
86 | for (index, condition) in conditions.enumerated() {
87 | conditionGroup.enter()
88 | condition.evaluateForOperation(operation) { result in
89 | results[index] = result
90 | conditionGroup.leave()
91 | }
92 | }
93 |
94 | // After all the conditions have evaluated, this block will execute.
95 | conditionGroup.notify(queue: DispatchQueue.global(qos: DispatchQoS.QoSClass.default)) {
96 | // Aggregate the errors that occurred, in order.
97 | var failures = results.compactMap { $0?.error }
98 |
99 | /*
100 | If any of the conditions caused this operation to be cancelled,
101 | check for that.
102 | */
103 | if operation.isCancelled {
104 | failures.append(NSError(code: .conditionFailed))
105 | }
106 |
107 | completion(failures)
108 | }
109 | }
110 | }
111 |
112 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Conditions/OperationErrors.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file defines the error codes and convenience functions for interacting with Operation-related errors.
7 | */
8 |
9 | import Foundation
10 |
11 | let OperationErrorDomain = "OperationErrors"
12 |
13 | enum OperationErrorCode: Int {
14 | case conditionFailed = 1
15 | case executionFailed = 2
16 | }
17 |
18 | extension NSError {
19 | convenience init(code: OperationErrorCode, userInfo: [String: Any]? = nil) {
20 | self.init(domain: OperationErrorDomain, code: code.rawValue, userInfo: userInfo)
21 | }
22 | }
23 |
24 | // This makes it easy to compare an `NSError.code` to an `OperationErrorCode`.
25 | func ==(lhs: Int, rhs: OperationErrorCode) -> Bool {
26 | return lhs == rhs.rawValue
27 | }
28 |
29 | func ==(lhs: OperationErrorCode, rhs: Int) -> Bool {
30 | return lhs.rawValue == rhs
31 | }
32 |
33 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Conditions/ReachabilityCondition.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file shows an example of implementing the OperationCondition protocol.
7 | */
8 |
9 | import Foundation
10 | import SystemConfiguration
11 |
12 | /**
13 | This is a condition that performs a very high-level reachability check.
14 | It does *not* perform a long-running reachability check, nor does it respond to changes in reachability.
15 | Reachability is evaluated once when the operation to which this is attached is asked about its readiness.
16 | */
17 | struct ReachabilityCondition: OperationCondition {
18 | static let hostKey = "Host"
19 | static let name = "Reachability"
20 | static let isMutuallyExclusive = false
21 |
22 | let host: URL
23 |
24 | init(host: URL) {
25 | self.host = host
26 | }
27 |
28 | func dependencyForOperation(_ operation: Operation) -> Foundation.Operation? {
29 | return nil
30 | }
31 |
32 | func evaluateForOperation(_ operation: Operation, completion: @escaping (OperationConditionResult) -> Void) {
33 | ReachabilityController.requestReachability(host) { reachable in
34 | if reachable {
35 | completion(.satisfied)
36 | }
37 | else {
38 | let error = NSError(code: .conditionFailed, userInfo: [
39 | OperationConditionKey: type(of: self).name,
40 | type(of: self).hostKey: self.host
41 | ])
42 |
43 | completion(.failed(error))
44 | }
45 | }
46 | }
47 |
48 | }
49 |
50 | /// A private singleton that maintains a basic cache of `SCNetworkReachability` objects.
51 | private class ReachabilityController {
52 | static var reachabilityRefs = [String: SCNetworkReachability]()
53 |
54 | static let reachabilityQueue = DispatchQueue(label: "Operations.Reachability", attributes: [])
55 |
56 | static func requestReachability(_ url: URL, completionHandler: @escaping (Bool) -> Void) {
57 | if let host = url.host {
58 | reachabilityQueue.async {
59 | var ref = self.reachabilityRefs[host]
60 |
61 | if ref == nil {
62 | let hostString = host as NSString
63 | ref = SCNetworkReachabilityCreateWithName(nil, hostString.utf8String!)
64 | }
65 |
66 | if let ref = ref {
67 | self.reachabilityRefs[host] = ref
68 |
69 | var reachable = false
70 | var flags: SCNetworkReachabilityFlags = []
71 | if SCNetworkReachabilityGetFlags(ref, &flags) != false {
72 | /*
73 | Note that this is a very basic "is reachable" check.
74 | Your app may choose to allow for other considerations,
75 | such as whether or not the connection would require
76 | VPN, a cellular connection, etc.
77 | */
78 | reachable = flags.contains(.reachable)
79 | }
80 | completionHandler(reachable)
81 | }
82 | else {
83 | completionHandler(false)
84 | }
85 | }
86 | }
87 | else {
88 | completionHandler(false)
89 | }
90 | }
91 | }
92 |
93 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Foundation.Operation+Operation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | A convenient extension to Foundation.NSOperation.
7 | */
8 |
9 | import Foundation
10 |
11 | extension Foundation.Operation {
12 | /**
13 | Add a completion block to be executed after the `NSOperation` enters the
14 | "finished" state.
15 | */
16 | func addCompletionBlock(_ block: @escaping () -> Void) {
17 | if let existing = completionBlock {
18 | /*
19 | If we already have a completion block, we construct a new one by
20 | chaining them together.
21 | */
22 | completionBlock = {
23 | existing()
24 | block()
25 | }
26 | }
27 | else {
28 | completionBlock = block
29 | }
30 | }
31 |
32 | /// Add multiple depdendencies to the operation.
33 | func addDependencies(_ dependencies: [Foundation.Operation]) {
34 | for dependency in dependencies {
35 | addDependency(dependency)
36 | }
37 | }
38 | }
39 |
40 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/NSLock+Operations.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | An extension to NSLock to simplify executing critical code.
7 | */
8 |
9 | import Foundation
10 |
11 | extension NSLock {
12 | func withCriticalScope(_ block: () -> T) -> T {
13 | lock()
14 | let value = block()
15 | unlock()
16 | return value
17 | }
18 | }
19 |
20 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Observers/BlockObserver.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file shows how to implement the OperationObserver protocol.
7 | */
8 |
9 | import Foundation
10 |
11 | /**
12 | The `BlockObserver` is a way to attach arbitrary blocks to significant events
13 | in an `Operation`'s lifecycle.
14 | */
15 | struct BlockObserver: OperationObserver {
16 | // MARK: Properties
17 |
18 | fileprivate let startHandler: ((Operation) -> Void)?
19 | fileprivate let produceHandler: ((Operation, Foundation.Operation) -> Void)?
20 | fileprivate let finishHandler: ((Operation, [NSError]) -> Void)?
21 |
22 | init(startHandler: ((Operation) -> Void)? = nil, produceHandler: ((Operation, Foundation.Operation) -> Void)? = nil, finishHandler: ((Operation, [NSError]) -> Void)? = nil) {
23 | self.startHandler = startHandler
24 | self.produceHandler = produceHandler
25 | self.finishHandler = finishHandler
26 | }
27 |
28 | // MARK: OperationObserver
29 |
30 | func operationDidStart(_ operation: Operation) {
31 | startHandler?(operation)
32 | }
33 |
34 | func operation(_ operation: Operation, didProduceOperation newOperation: Foundation.Operation) {
35 | produceHandler?(operation, newOperation)
36 | }
37 |
38 | func operationDidFinish(_ operation: Operation, errors: [NSError]) {
39 | finishHandler?(operation, errors)
40 | }
41 | }
42 |
43 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Observers/NetworkObserver.swift:
--------------------------------------------------------------------------------
1 | /*
2 | NetworkObserver.swift
3 | MyOperationPractice
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/14/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | Contains the code to manage the visibility of the network activity indicator
10 | */
11 |
12 | import UIKit
13 |
14 | /**
15 | An `OperationObserver` that will cause the network activity indicatior to appear as long
16 | as the `Operation` to which it is attached is executing.
17 | */
18 |
19 | struct NetworkObserver: OperationObserver {
20 | //MARk: Initialization
21 |
22 | init() { }
23 |
24 | func operationDidStart(_ operation: Operation) {
25 | DispatchQueue.main.async {
26 | // Increment the network indicator's "reference count"
27 | NetworkIndicatorController.shared.networkActivityDidStart()
28 | }
29 | }
30 |
31 | func operation(_ operation: Operation, didProduceOperation newOperation: Foundation.Operation) { }
32 |
33 | func operationDidFinish(_ operation: Operation, errors: [NSError]) {
34 | DispatchQueue.main.async {
35 | // Decrement the network indicator's "reference count".
36 | NetworkIndicatorController.shared.networkActivityDidEnd()
37 | }
38 | }
39 | }
40 |
41 |
42 | /// A singleton to manage a visual "reference count" on the network activity indicator.
43 | private class NetworkIndicatorController {
44 | // MARK: Properties
45 |
46 | static let shared = NetworkIndicatorController()
47 |
48 | fileprivate var activityCount = 0
49 |
50 | fileprivate var visibilityTimer: Timer?
51 |
52 | // MARK: Methods
53 |
54 | func networkActivityDidStart() {
55 | assert(Thread.isMainThread, "Alerting network activity indicator state can only be done on the main thread.")
56 |
57 | activityCount += 1
58 |
59 | updateIndicatorVisibility()
60 | }
61 |
62 | func networkActivityDidEnd() {
63 | assert(Thread.isMainThread, "Alerting network activity indicator state can only be done on the main thread.")
64 |
65 | activityCount -= 1
66 |
67 | updateIndicatorVisibility()
68 | }
69 |
70 | func updateIndicatorVisibility() {
71 | if activityCount > 0 {
72 | showIndicator()
73 | }
74 | else {
75 | /*
76 | To perevent the indicator from flickering on and off, we delay the hiding
77 | of the indicator by one second. This provides the chance to come in and
78 | invalidate the timer before it fires.
79 | */
80 | visibilityTimer = Timer(interval: 1.0) {
81 | self.hideIndicator()
82 | }
83 | }
84 | }
85 |
86 | fileprivate func showIndicator() {
87 | visibilityTimer?.cancel()
88 | visibilityTimer = nil
89 | UIApplication.shared.isNetworkActivityIndicatorVisible = true
90 | }
91 |
92 | fileprivate func hideIndicator() {
93 | visibilityTimer?.cancel()
94 | visibilityTimer = nil
95 | UIApplication.shared.isNetworkActivityIndicatorVisible = false
96 | }
97 | }
98 |
99 |
100 | /// Essentially a cancelable `dispatch_after`.
101 | class Timer {
102 | //MARK: Properties
103 |
104 | fileprivate var isCancelled = false
105 |
106 | //MARK: Initialiazation
107 |
108 | init(interval: TimeInterval, handler: @escaping () -> Void) {
109 | let when = DispatchTime.now() + Double(Int64(interval * Double(NSEC_PER_SEC))) / Double(NSEC_PER_SEC)
110 |
111 | DispatchQueue.main.asyncAfter(deadline: when) { [weak self] in
112 | if self?.isCancelled == false {
113 | handler()
114 | }
115 | }
116 | }
117 |
118 | func cancel() {
119 | isCancelled = true
120 | }
121 | }
122 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Observers/OperationObserver.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file defines the OperationObserver protocol.
7 | */
8 |
9 | import Foundation
10 |
11 | /**
12 | The protocol that types may implement if they wish to be notified of significant
13 | operation lifecycle events.
14 | */
15 | protocol OperationObserver {
16 |
17 | /// Invoked immediately prior to the `Operation`'s `execute()` method.
18 | func operationDidStart(_ operation: Operation)
19 |
20 | /// Invoked when `Operation.produceOperation(_:)` is executed.
21 | func operation(_ operation: Operation, didProduceOperation newOperation: Foundation.Operation)
22 |
23 | /**
24 | Invoked as an `Operation` finishes, along with any errors produced during
25 | execution (or readiness evaluation).
26 | */
27 | func operationDidFinish(_ operation: Operation, errors: [NSError])
28 |
29 | }
30 |
31 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/OperationQueue/ExclusivityController.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the code to automatically set up dependencies between mutually exclusive operations.
7 | */
8 |
9 | import Foundation
10 |
11 | /**
12 | `ExclusivityController` is a singleton to keep track of all the in-flight
13 | `Operation` instances that have declared themselves as requiring mutual exclusivity.
14 | We use a singleton because mutual exclusivity must be enforced across the entire
15 | app, regardless of the `OperationQueue` on which an `Operation` was executed.
16 | */
17 | class ExclusivityController {
18 | static let shared = ExclusivityController()
19 |
20 | fileprivate let serialQueue = DispatchQueue(label: "Operations.ExclusivityController", attributes: [])
21 | fileprivate var operations: [String: [Operation]] = [:]
22 |
23 | fileprivate init() {
24 | /*
25 | A private initializer effectively prevents any other part of the app
26 | from accidentally creating an instance.
27 | */
28 | }
29 |
30 | /// Registers an operation as being mutually exclusive
31 | func addOperation(_ operation: Operation, categories: [String]) {
32 | /*
33 | This needs to be a synchronous operation.
34 | If this were async, then we might not get around to adding dependencies
35 | until after the operation had already begun, which would be incorrect.
36 | */
37 | serialQueue.sync {
38 | for category in categories {
39 | self.noqueue_addOperation(operation, category: category)
40 | }
41 | }
42 | }
43 |
44 | /// Unregisters an operation from being mutually exclusive.
45 | func removeOperation(_ operation: Operation, categories: [String]) {
46 | serialQueue.async {
47 | for category in categories {
48 | self.noqueue_removeOperation(operation, category: category)
49 | }
50 | }
51 | }
52 |
53 |
54 | // MARK: Operation Management
55 |
56 | fileprivate func noqueue_addOperation(_ operation: Operation, category: String) {
57 | var operationsWithThisCategory = operations[category] ?? []
58 |
59 | if let last = operationsWithThisCategory.last {
60 | operation.addDependency(last)
61 | }
62 |
63 | operationsWithThisCategory.append(operation)
64 |
65 | operations[category] = operationsWithThisCategory
66 | }
67 |
68 | fileprivate func noqueue_removeOperation(_ operation: Operation, category: String) {
69 | let matchingOperations = operations[category]
70 |
71 | if var operationsWithThisCategory = matchingOperations,
72 | let index = operationsWithThisCategory.firstIndex(of: operation) {
73 |
74 | operationsWithThisCategory.remove(at: index)
75 | operations[category] = operationsWithThisCategory
76 | }
77 | }
78 |
79 | }
80 |
81 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/OperationQueue/OperationQueue.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains an NSOperationQueue subclass.
7 | */
8 |
9 | import Foundation
10 |
11 | /**
12 | The delegate of an `OperationQueue` can respond to `Operation` lifecycle
13 | events by implementing these methods.
14 |
15 | In general, implementing `OperationQueueDelegate` is not necessary; you would
16 | want to use an `OperationObserver` instead. However, there are a couple of
17 | situations where using `OperationQueueDelegate` can lead to simpler code.
18 | For example, `GroupOperation` is the delegate of its own internal
19 | `OperationQueue` and uses it to manage dependencies.
20 | */
21 | @objc protocol OperationQueueDelegate: NSObjectProtocol {
22 | @objc optional func operationQueue(_ operationQueue: OperationQueue, willAddOperation operation: Foundation.Operation)
23 | @objc optional func operationQueue(_ operationQueue: OperationQueue, operationDidFinish operation: Foundation.Operation, withErrors errors: [NSError])
24 | }
25 |
26 | /**
27 | `OperationQueue` is an `NSOperationQueue` subclass that implements a large
28 | number of "extra features" related to the `Operation` class:
29 |
30 | - Notifying a delegate of all operation completion
31 | - Extracting generated dependencies from operation conditions
32 | - Setting up dependencies to enforce mutual exclusivity
33 | */
34 | class OperationQueue: Foundation.OperationQueue {
35 | weak var delegate: OperationQueueDelegate?
36 |
37 | override func addOperation(_ op: Foundation.Operation) {
38 | if let op = op as? Operation {
39 | // Set up a `BlockObserver` to invoke the `OperationQueueDelegate` method.
40 | let delegate = BlockObserver(
41 | startHandler: nil,
42 | produceHandler: { [weak self] in
43 | self?.addOperation($1)
44 | },
45 | finishHandler: { [weak self] in
46 | if let q = self {
47 | q.delegate?.operationQueue?(q, operationDidFinish: $0, withErrors: $1)
48 | }
49 | }
50 | )
51 | op.addObserver(delegate)
52 |
53 | // Extract any dependencies needed by this operation.
54 | let dependencies = op.conditions.compactMap {
55 | $0.dependencyForOperation(op)
56 | }
57 |
58 | for dependency in dependencies {
59 | op.addDependency(dependency)
60 |
61 | self.addOperation(dependency)
62 | }
63 |
64 | /*
65 | With condition dependencies added, we can now see if this needs
66 | dependencies to enforce mutual exclusivity.
67 | */
68 | let concurrencyCategories: [String] = op.conditions.compactMap { condition in
69 | if !type(of: condition).isMutuallyExclusive { return nil }
70 |
71 | return "\(type(of: condition))"
72 | }
73 |
74 | if !concurrencyCategories.isEmpty {
75 | // Set up the mutual exclusivity dependencies.
76 | let exclusivityController = ExclusivityController.shared
77 |
78 | exclusivityController.addOperation(op, categories: concurrencyCategories)
79 |
80 | op.addObserver(BlockObserver(finishHandler: { operation, _ in
81 | exclusivityController.removeOperation(operation, categories: concurrencyCategories)
82 | }))
83 | }
84 |
85 | /*
86 | Indicate to the operation that we've finished our extra work on it
87 | and it's now it a state where it can proceed with evaluating conditions,
88 | if appropriate.
89 | */
90 | op.willEnqueue()
91 | }
92 | else {
93 | /*
94 | For regular `NSOperation`s, we'll manually call out to the queue's
95 | delegate we don't want to just capture "operation" because that
96 | would lead to the operation strongly referencing itself and that's
97 | the pure definition of a memory leak.
98 | */
99 | op.addCompletionBlock { [weak self, weak op] in
100 | guard let queue = self, let operation = op else { return }
101 | queue.delegate?.operationQueue?(queue, operationDidFinish: operation, withErrors: [])
102 | }
103 | }
104 |
105 | delegate?.operationQueue?(self, willAddOperation: op)
106 |
107 | super.addOperation(op)
108 | }
109 |
110 | override func addOperations(_ ops: [Foundation.Operation], waitUntilFinished wait: Bool) {
111 | /*
112 | The base implementation of this method does not call `addOperation()`,
113 | so we'll call it ourselves.
114 | */
115 | for operation in ops {
116 | addOperation(operation)
117 | }
118 |
119 | if wait {
120 | for operation in ops {
121 | operation.waitUntilFinished()
122 | }
123 | }
124 | }
125 | }
126 |
127 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Operations/AlertOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file shows how to present an alert as part of an operation.
7 | */
8 |
9 | import UIKit
10 |
11 | class AlertOperation: Operation {
12 | // MARK: Properties
13 |
14 | fileprivate let alertController = UIAlertController(title: nil, message: nil, preferredStyle: .alert)
15 | fileprivate let presentationContext: UIViewController?
16 |
17 | var title: String? {
18 | get {
19 | return alertController.title
20 | }
21 |
22 | set {
23 | alertController.title = newValue
24 | name = newValue
25 | }
26 | }
27 |
28 | var message: String? {
29 | get {
30 | return alertController.message
31 | }
32 |
33 | set {
34 | alertController.message = newValue
35 | }
36 | }
37 |
38 | // MARK: Initialization
39 |
40 | init(presentationContext: UIViewController? = nil) {
41 | self.presentationContext = presentationContext ?? UIApplication.shared.keyWindow?.rootViewController
42 |
43 | super.init()
44 |
45 | addCondition(AlertPresentation())
46 |
47 | /*
48 | This operation modifies the view controller hierarchy.
49 | Doing this while other such operations are executing can lead to
50 | inconsistencies in UIKit. So, let's make them mutally exclusive.
51 | */
52 | addCondition(MutuallyExclusive())
53 | }
54 |
55 | func addAction(_ title: String, style: UIAlertActionStyle = .default, handler: @escaping (AlertOperation) -> Void = { _ in }) {
56 | let action = UIAlertAction(title: title, style: style) { [weak self] _ in
57 | if let strongSelf = self {
58 | handler(strongSelf)
59 | }
60 |
61 | self?.finish()
62 | }
63 |
64 | alertController.addAction(action)
65 | }
66 |
67 | override func execute() {
68 | guard let presentationContext = presentationContext else {
69 | finish()
70 |
71 | return
72 | }
73 |
74 | DispatchQueue.main.async {
75 | if self.alertController.actions.isEmpty {
76 | self.addAction("OK")
77 | }
78 |
79 | presentationContext.present(self.alertController, animated: true, completion: nil)
80 | }
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Operations/BlockOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This code shows how to create a simple subclass of Operation.
7 | */
8 |
9 | import Foundation
10 |
11 | /// A closure type that takes a closure as its parameter.
12 | typealias OperationBlock = (@escaping () -> Void) -> Void
13 |
14 | /// A sublcass of `Operation` to execute a closure.
15 | class BlockOperation: Operation {
16 | fileprivate let block: OperationBlock?
17 |
18 | /**
19 | The designated initializer.
20 |
21 | - parameter block: The closure to run when the operation executes. This
22 | closure will be run on an arbitrary queue. The parameter passed to the
23 | block **MUST** be invoked by your code, or else the `BlockOperation`
24 | will never finish executing. If this parameter is `nil`, the operation
25 | will immediately finish.
26 | */
27 | init(block: OperationBlock? = nil) {
28 | self.block = block
29 | super.init()
30 | }
31 |
32 | /**
33 | A convenience initializer to execute a block on the main queue.
34 |
35 | - parameter mainQueueBlock: The block to execute on the main queue. Note
36 | that this block does not have a "continuation" block to execute (unlike
37 | the designated initializer). The operation will be automatically ended
38 | after the `mainQueueBlock` is executed.
39 | */
40 | convenience init(mainQueueBlock: @escaping ()->()) {
41 | self.init(block: { continuation in
42 | DispatchQueue.main.async {
43 | mainQueueBlock()
44 | continuation()
45 | }
46 | })
47 | }
48 |
49 | override func execute() {
50 | guard let block = block else {
51 | finish()
52 | return
53 | }
54 |
55 | block {
56 | self.finish()
57 | }
58 | }
59 | }
60 |
61 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Operations/GroupOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file shows how operations can be composed together to form new operations.
7 | */
8 |
9 | import Foundation
10 |
11 | /**
12 | A subclass of `Operation` that executes zero or more operations as part of its
13 | own execution. This class of operation is very useful for abstracting several
14 | smaller operations into a larger operation. As an example, the `GetEarthquakesOperation`
15 | is composed of both a `DownloadEarthquakesOperation` and a `ParseEarthquakesOperation`.
16 |
17 | Additionally, `GroupOperation`s are useful if you establish a chain of dependencies,
18 | but part of the chain may "loop". For example, if you have an operation that
19 | requires the user to be authenticated, you may consider putting the "login"
20 | operation inside a group operation. That way, the "login" operation may produce
21 | subsequent operations (still within the outer `GroupOperation`) that will all
22 | be executed before the rest of the operations in the initial chain of operations.
23 | */
24 | class GroupOperation: Operation {
25 | fileprivate let internalQueue = OperationQueue()
26 | fileprivate let startingOperation = Foundation.BlockOperation(block: {})
27 | fileprivate let finishingOperation = Foundation.BlockOperation(block: {})
28 |
29 | fileprivate var aggregatedErrors = [NSError]()
30 | fileprivate let serialQueue = DispatchQueue(label: "GroupOperation_Serial_Queue")
31 |
32 | convenience init(operations: Foundation.Operation...) {
33 | self.init(operations: operations)
34 | }
35 |
36 | init(operations: [Foundation.Operation]) {
37 | super.init()
38 |
39 | internalQueue.isSuspended = true
40 | internalQueue.delegate = self
41 | internalQueue.addOperation(startingOperation)
42 |
43 | for operation in operations {
44 | internalQueue.addOperation(operation)
45 | }
46 | }
47 |
48 | func limitMaxConcurrentOperations(to maxCount: Int) {
49 | self.internalQueue.maxConcurrentOperationCount = maxCount
50 | }
51 |
52 | override func cancel() {
53 | internalQueue.cancelAllOperations()
54 | super.cancel()
55 | }
56 |
57 | override func execute() {
58 | internalQueue.isSuspended = false
59 | internalQueue.addOperation(finishingOperation)
60 | }
61 |
62 | func addOperation(_ operation: Foundation.Operation) {
63 | internalQueue.addOperation(operation)
64 | }
65 |
66 | func addOperations(_ operations: [Foundation.Operation]) {
67 | for operation in operations {
68 | internalQueue.addOperation(operation)
69 | }
70 | }
71 |
72 |
73 | /**
74 | Note that some part of execution has produced an error.
75 | Errors aggregated through this method will be included in the final array
76 | of errors reported to observers and to the `finished(_:)` method.
77 | */
78 | final func aggregateError(_ error: NSError) {
79 | aggregatedErrors.append(error)
80 | }
81 |
82 | func operationDidFinish(_ operation: Foundation.Operation, withErrors errors: [NSError]) {
83 | // For use by subclasses.
84 | }
85 | }
86 |
87 | extension GroupOperation: OperationQueueDelegate {
88 | final func operationQueue(_ operationQueue: OperationQueue, willAddOperation operation: Foundation.Operation) {
89 | assert(!finishingOperation.isFinished && !finishingOperation.isExecuting, "cannot add new operations to a group after the group has completed")
90 |
91 | /*
92 | Some operation in this group has produced a new operation to execute.
93 | We want to allow that operation to execute before the group completes,
94 | so we'll make the finishing operation dependent on this newly-produced operation.
95 | */
96 | if operation !== finishingOperation {
97 | finishingOperation.addDependency(operation)
98 | }
99 |
100 | /*
101 | All operations should be dependent on the "startingOperation".
102 | This way, we can guarantee that the conditions for other operations
103 | will not evaluate until just before the operation is about to run.
104 | Otherwise, the conditions could be evaluated at any time, even
105 | before the internal operation queue is unsuspended.
106 | */
107 | if operation !== startingOperation {
108 | operation.addDependency(startingOperation)
109 | }
110 | }
111 |
112 | final func operationQueue(_ operationQueue: OperationQueue, operationDidFinish operation: Foundation.Operation, withErrors errors: [NSError]) {
113 | self.serialQueue.async {
114 | self.aggregatedErrors.append(contentsOf: errors)
115 | }
116 |
117 | if operation === finishingOperation {
118 | internalQueue.isSuspended = true
119 | self.serialQueue.sync {
120 | finish(aggregatedErrors)
121 | }
122 | }
123 | else if operation !== startingOperation {
124 | operationDidFinish(operation, withErrors: errors)
125 | }
126 | }
127 | }
128 |
129 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Operations/Operation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | This file contains the foundational subclass of NSOperation.
7 | */
8 |
9 | import Foundation
10 |
11 |
12 |
13 | @objc private extension Operation {
14 | /**
15 | Add the "state" key to the key value observable properties of `Foundation.Operation`.
16 | */
17 | class func keyPathsForValuesAffectingIsReady() -> Set {
18 | return ["state"]
19 | }
20 |
21 | class func keyPathsForValuesAffectingIsExecuting() -> Set {
22 | return ["state"]
23 | }
24 |
25 | class func keyPathsForValuesAffectingIsFinished() -> Set {
26 | return ["state"]
27 | }
28 |
29 | // class func keyPathsForValuesAffectingIsCancelled() -> Set {
30 | // return ["state"]
31 | // }
32 | }
33 |
34 | /**
35 | The subclass of `NSOperation` from which all other operations should be derived.
36 | This class adds both Conditions and Observers, which allow the operation to define
37 | extended readiness requirements, as well as notify many interested parties
38 | about interesting operation state changes
39 | */
40 | class Operation: Foundation.Operation {
41 |
42 | // MARK: State Management
43 |
44 | fileprivate enum State: Int, Comparable {
45 | /// The initial state of an `Operation`.
46 | case initialized
47 |
48 | /// The `Operation` is ready to begin evaluating conditions.
49 | case pending
50 |
51 | /// The `Operation` is evaluating conditions.
52 | case evaluatingConditions
53 |
54 | /**
55 | The `Operation`'s conditions have all been satisfied, and it is ready
56 | to execute.
57 | */
58 | case ready
59 |
60 | /// The `Operation` is executing.
61 | case executing
62 |
63 | /**
64 | Execution of the `Operation` has finished, but it has not yet notified
65 | the queue of this.
66 | */
67 | case finishing
68 |
69 | /// The `Operation` has finished executing.
70 | case finished
71 |
72 | func canTransitionToState(_ target: State) -> Bool {
73 | switch (self, target) {
74 | case (.initialized, .pending):
75 | return true
76 | case (.pending, .evaluatingConditions):
77 | return true
78 | case (.evaluatingConditions, .ready):
79 | return true
80 | case (.ready, .executing):
81 | return true
82 | case (.ready, .finishing):
83 | return true
84 | case (.executing, .finishing):
85 | return true
86 | case (.finishing, .finished):
87 | return true
88 | default:
89 | return false
90 | }
91 | }
92 | }
93 |
94 | /**
95 | Indicates that the Operation can now begin to evaluate readiness conditions,
96 | if appropriate.
97 | */
98 | func willEnqueue() {
99 | state = .pending
100 | }
101 |
102 | /// Private storage for the `state` property that will be KVO observed.
103 | fileprivate var _state = State.initialized
104 |
105 | /// A lock to guard reads and writes to the `_state` property
106 | fileprivate let stateLock = NSLock()
107 |
108 | fileprivate var state: State {
109 | get {
110 | return stateLock.withCriticalScope {
111 | _state
112 | }
113 | }
114 |
115 | set(newState) {
116 | /*
117 | It's important to note that the KVO notifications are NOT called from inside
118 | the lock. If they were, the app would deadlock, because in the middle of
119 | calling the `didChangeValueForKey()` method, the observers try to access
120 | properties like "isReady" or "isFinished". Since those methods also
121 | acquire the lock, then we'd be stuck waiting on our own lock. It's the
122 | classic definition of deadlock.
123 | */
124 |
125 | // let stateKeyPath = \Operation.state
126 | // willChangeValue(for: stateKeyPath)
127 | willChangeValue(forKey: "state")
128 |
129 | stateLock.withCriticalScope { () -> Void in
130 | guard _state != .finished else {
131 | return
132 | }
133 |
134 | assert(_state.canTransitionToState(newState), "Performing invalid state transition.")
135 | _state = newState
136 | }
137 | // didChangeValue(for: stateKeyPath)
138 | didChangeValue(forKey: "state")
139 | }
140 | }
141 |
142 | // Here is where we extend our definition of "readiness".
143 | override var isReady: Bool {
144 | switch state {
145 |
146 | case .initialized:
147 | // If the operation has been cancelled, "isReady" should return true
148 | return isCancelled
149 |
150 | case .pending:
151 | // If the operation has been cancelled, "isReady" should return true
152 | guard !isCancelled else {
153 | return true
154 | }
155 |
156 | // If super isReady, conditions can be evaluated
157 | if super.isReady {
158 | evaluateConditions()
159 | }
160 |
161 | // Until conditions have been evaluated, "isReady" returns false
162 | return false
163 |
164 | case .ready:
165 | return super.isReady || isCancelled
166 |
167 | default:
168 | return false
169 | }
170 | }
171 |
172 | var userInitiated: Bool {
173 | get {
174 | return qualityOfService == .userInitiated
175 | }
176 |
177 | set {
178 | assert(state < .executing, "Cannot modify userInitiated after execution has begun.")
179 |
180 | qualityOfService = newValue ? .userInitiated : .default
181 | }
182 | }
183 |
184 | override var isExecuting: Bool {
185 | return state == .executing
186 | }
187 |
188 | override var isFinished: Bool {
189 | return state == .finished
190 | }
191 |
192 | fileprivate func evaluateConditions() {
193 | assert(state == .pending && !isCancelled, "evaluateConditions() was called out-of-order")
194 |
195 | state = .evaluatingConditions
196 |
197 | guard conditions.count > 0 else {
198 | state = .ready
199 | return
200 | }
201 |
202 | OperationConditionEvaluator.evaluate(conditions, operation: self) { failures in
203 | self._internalErrors += failures
204 | self.state = .ready
205 | }
206 | }
207 |
208 | // MARK: Observers and Conditions
209 |
210 | fileprivate(set) var conditions = [OperationCondition]()
211 |
212 | func addCondition(_ condition: OperationCondition) {
213 | assert(state < .evaluatingConditions, "Cannot modify conditions after execution has begun.")
214 |
215 | conditions.append(condition)
216 | }
217 |
218 | fileprivate(set) var observers = [OperationObserver]()
219 |
220 | func addObserver(_ observer: OperationObserver) {
221 | assert(state < .executing, "Cannot modify observers after execution has begun.")
222 |
223 | observers.append(observer)
224 | }
225 |
226 | override func addDependency(_ operation: Foundation.Operation) {
227 | assert(state < .executing, "Dependencies cannot be modified after execution has begun.")
228 |
229 | super.addDependency(operation)
230 | }
231 |
232 | // MARK: Execution and Cancellation
233 |
234 | override final func start() {
235 | // NSOperation.start() contains important logic that shouldn't be bypassed.
236 | super.start()
237 |
238 | // If the operation has been cancelled, we still need to enter the "Finished" state.
239 | if isCancelled {
240 | finish()
241 | }
242 | }
243 |
244 | override final func main() {
245 | assert(state == .ready, "This operation must be performed on an operation queue.")
246 |
247 | if _internalErrors.isEmpty && !isCancelled {
248 | state = .executing
249 |
250 | for observer in observers {
251 | observer.operationDidStart(self)
252 | }
253 |
254 | print("Operation \(self.name ?? "has no name") executed")
255 |
256 | execute()
257 | }
258 | else {
259 | finish()
260 | }
261 | }
262 |
263 | /**
264 | `execute()` is the entry point of execution for all `Operation` subclasses.
265 | If you subclass `Operation` and wish to customize its execution, you would
266 | do so by overriding the `execute()` method.
267 |
268 | At some point, your `Operation` subclass must call one of the "finish"
269 | methods defined below; this is how you indicate that your operation has
270 | finished its execution, and that operations dependent on yours can re-evaluate
271 | their readiness state.
272 | */
273 | func execute() {
274 | print("\(type(of: self)) must override `execute()`.")
275 |
276 | finish()
277 | }
278 |
279 | fileprivate var _internalErrors = [NSError]()
280 | func cancelWithError(_ error: NSError? = nil) {
281 | if let error = error {
282 | _internalErrors.append(error)
283 | }
284 |
285 | cancel()
286 | }
287 |
288 | final func produceOperation(_ operation: Foundation.Operation) {
289 | for observer in observers {
290 | observer.operation(self, didProduceOperation: operation)
291 | }
292 | }
293 |
294 | // MARK: Finishing
295 |
296 | /**
297 | Most operations may finish with a single error, if they have one at all.
298 | This is a convenience method to simplify calling the actual `finish()`
299 | method. This is also useful if you wish to finish with an error provided
300 | by the system frameworks. As an example, see `DownloadEarthquakesOperation`
301 | for how an error from an `NSURLSession` is passed along via the
302 | `finishWithError()` method.
303 | */
304 | final func finishWithError(_ error: NSError?) {
305 | if let error = error {
306 | finish([error])
307 | }
308 | else {
309 | finish()
310 | }
311 | }
312 |
313 | /**
314 | A private property to ensure we only notify the observers once that the
315 | operation has finished.
316 | */
317 | fileprivate var hasFinishedAlready = false
318 |
319 | final func finish(_ errors: [NSError] = []) {
320 | if !hasFinishedAlready {
321 | hasFinishedAlready = true
322 | state = .finishing
323 |
324 | let combinedErrors = _internalErrors + errors
325 | finished(combinedErrors)
326 | print("Operation \(self.name ?? "has no name") finished")
327 |
328 | for observer in observers {
329 | observer.operationDidFinish(self, errors: combinedErrors)
330 | }
331 |
332 | state = .finished
333 | }
334 | }
335 |
336 | /**
337 | Subclasses may override `finished(_:)` if they wish to react to the operation
338 | finishing with errors. For example, the `LoadModelOperation` implements
339 | this method to potentially inform the user about an error when trying to
340 | bring up the Core Data stack.
341 | */
342 | func finished(_ errors: [NSError]) {
343 | // No op.
344 | }
345 |
346 | override final func waitUntilFinished() {
347 | /*
348 | Waiting on operations is almost NEVER the right thing to do. It is
349 | usually superior to use proper locking constructs, such as `dispatch_semaphore_t`
350 | or `dispatch_group_notify`, or even `NSLocking` objects. Many developers
351 | use waiting when they should instead be chaining discrete operations
352 | together using dependencies.
353 |
354 | To reinforce this idea, invoking `waitUntilFinished()` will crash your
355 | app, as incentive for you to find a more appropriate way to express
356 | the behavior you're wishing to create.
357 | */
358 | fatalError("Waiting on operations is an anti-pattern. Remove this ONLY if you're absolutely sure there is No Other Way™.")
359 | }
360 | }
361 |
362 | // Simple operator functions to simplify the assertions used above.
363 | private func <(lhs: Operation.State, rhs: Operation.State) -> Bool {
364 | return lhs.rawValue < rhs.rawValue
365 | }
366 |
367 | private func ==(lhs: Operation.State, rhs: Operation.State) -> Bool {
368 | return lhs.rawValue == rhs.rawValue
369 | }
370 |
371 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Operations/Operations/URLSessionTaskOperation.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Copyright (C) 2015 Apple Inc. All Rights Reserved.
3 | See LICENSE.txt for this sample’s licensing information
4 |
5 | Abstract:
6 | Shows how to lift operation-like objects in to the NSOperation world.
7 | */
8 |
9 | import Foundation
10 |
11 | private var URLSessionTaksOperationKVOContext = 0
12 |
13 | /**
14 | `URLSessionTaskOperation` is an `Operation` that lifts an `NSURLSessionTask`
15 | into an operation.
16 |
17 | Note that this operation does not participate in any of the delegate callbacks \
18 | of an `NSURLSession`, but instead uses Key-Value-Observing to know when the
19 | task has been completed. It also does not get notified about any errors that
20 | occurred during execution of the task.
21 |
22 | An example usage of `URLSessionTaskOperation` can be seen in the `DownloadEarthquakesOperation`.
23 | */
24 | class URLSessionTaskOperation: Operation {
25 | let task: URLSessionTask
26 |
27 | init(task: URLSessionTask) {
28 | assert(task.state == .suspended, "Tasks must be suspended.")
29 | self.task = task
30 | super.init()
31 | name = "URLSessionTaskOperation"
32 | }
33 |
34 | override func execute() {
35 |
36 | assert(task.state == .suspended, "Task was resumed by something other than \(self).")
37 |
38 | task.addObserver(self, forKeyPath: "state", options: [], context: &URLSessionTaksOperationKVOContext)
39 |
40 | task.resume()
41 | }
42 |
43 | override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
44 | guard context == &URLSessionTaksOperationKVOContext else { return }
45 |
46 | if object as AnyObject === task && keyPath == "state" && task.state == .completed {
47 | task.removeObserver(self, forKeyPath: "state")
48 | finish()
49 | }
50 | }
51 |
52 | override func cancel() {
53 | task.cancel()
54 | super.cancel()
55 | }
56 | }
57 |
58 |
--------------------------------------------------------------------------------
/WWDCSubGetter/ParseHtmlVideoPageOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // ParseHtmlVideoPageOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | enum SessionDataTypes: Hashable {
12 |
13 |
14 | case video(VideoQuality), pdf, sampleCode
15 | }
16 |
17 | class ParseHtmlVideoPageOperation: Operation {
18 | let cacheFile: URL
19 |
20 | let types: [SessionDataTypes]
21 | let sessionNumber: String
22 |
23 |
24 | init(for types: [SessionDataTypes], sessionNumber: String, cacheFile: URL) {
25 | self.cacheFile = cacheFile
26 | self.types = types
27 | self.sessionNumber = sessionNumber
28 | super.init()
29 |
30 | name = "ParseHtmlVideoPageOperation \(sessionNumber)"
31 |
32 | }
33 |
34 | override func execute() {
35 | executeOld()
36 | }
37 |
38 | func executeOld() {
39 |
40 | do {
41 | let data = try Data(contentsOf: self.cacheFile, options: Data.ReadingOptions.mappedIfSafe)
42 |
43 | let htmlText = String.init(data: data, encoding:
44 | .utf8)!
45 | // let quality: VideoQuality = self.types.contains(SessionDataTypes.video(.sd)) ? .sd : .hd
46 |
47 | // if !videoURLString.isEmpty {
48 |
49 | for type in types {
50 |
51 | switch type {
52 | case let .video(quality):
53 | let videoURLString = WWDCVideosController.getHDorSDdURLs(fromHTML: htmlText, format: quality)
54 |
55 | if quality == .hd {
56 | linksModel.hdVideosLinks.append(videoURLString)
57 | }
58 | else {
59 | linksModel.sdVideosLinks.append(videoURLString)
60 | }
61 |
62 | case .pdf:
63 | let pdfURLStrings = WWDCVideosController.getPDFResourceURL(fromHTML: htmlText)
64 | linksModel.pdfLinks.append(contentsOf: pdfURLStrings)
65 |
66 | case .sampleCode:
67 | let sampleCodesURLStrings = WWDCVideosController.getSampleCodeURL(fromHTML: htmlText)
68 | let sampleCodesURLStrings2 = WWDCVideosController.getSampleCodeURL2(fromHTML: htmlText)
69 | let sampleCodesURLStrings3 = WWDCVideosController.getSampleCodeURL3(fromHTML: htmlText)
70 |
71 | linksModel.sampleCodesLinks.append(contentsOf: sampleCodesURLStrings)
72 | linksModel.sampleCodesLinks.append(contentsOf: sampleCodesURLStrings2)
73 | linksModel.sampleCodesLinks.append(contentsOf: sampleCodesURLStrings3)
74 |
75 |
76 | }
77 | }
78 |
79 | // }
80 | // else {
81 | // try FileManager.default.removeItem(at: self.cacheFile)
82 | // }
83 |
84 | finish()
85 |
86 | }
87 | catch {
88 | print(error.localizedDescription)
89 | finish([error] as [NSError])
90 | }
91 |
92 | }
93 |
94 | func executeNew() {
95 |
96 | }
97 |
98 | }
99 |
--------------------------------------------------------------------------------
/WWDCSubGetter/ParseSessionsListOperation.swift:
--------------------------------------------------------------------------------
1 | //
2 | // PrseSessionsListOperation.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/6/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 |
12 | class ParseSessionsListOperation: Operation {
13 |
14 | //MARK: Properties
15 |
16 | let sessionListURL: URL
17 | let wwdcYear: WWDC
18 |
19 | //MARK: Initializer
20 |
21 | init(for wwdcYear: WWDC, cacheFile: URL) {
22 |
23 | self.sessionListURL = cacheFile
24 | self.wwdcYear = wwdcYear
25 |
26 | super.init()
27 | self.name = "ParseSessionsListOperation"
28 | }
29 |
30 | override func execute() {
31 |
32 | do {
33 |
34 | var sessionsListArray: [String] = []
35 |
36 | let data = try Data(contentsOf: self.sessionListURL, options: Data.ReadingOptions.mappedIfSafe)
37 |
38 | let htmlSessionListString = String.init(data: data, encoding:
39 | String.Encoding.utf8)!
40 |
41 | sessionsListArray = WWDCVideosController.getSessionsList(fromHTML: htmlSessionListString, wwdcYear: wwdcYear.stringValue)
42 |
43 | //get unique values
44 | sessionsListArray = Array(Set(sessionsListArray)).sorted()
45 |
46 | linksModel.titles = sessionsListArray
47 |
48 | if !linksModel.titles.isEmpty {
49 | let titlesURL = linksModel.titlesCacheURLFor(wwdcYear)
50 | let text = linksModel.titles.removingDuplicates().sorted().joined(separator: "\n")
51 | try text.write(to: titlesURL, atomically: false, encoding: String.Encoding.utf8)
52 | }
53 |
54 | } catch {
55 |
56 | print(error)
57 | }
58 |
59 | linksModel.clear()
60 | finish()
61 | }
62 |
63 | }
64 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Presenter.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Presenter.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 1/22/19.
6 | // Copyright © 2019 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | class Presenter {
12 |
13 | let operationQueue = OperationQueue()
14 |
15 | func convertToSubtitle(from url: String, wwdc: WWDC, type: InputType, completion: @escaping ([Subtitle]) -> Void) {
16 | let convertToSubtitleOperation = ConvertToSubtitleOperation(from: url, wwdc: wwdc, type: type, completion: completion)
17 | self.operationQueue.addOperation(convertToSubtitleOperation)
18 | }
19 |
20 | func getSubtitles(completionHandler: @escaping () -> Void) {
21 |
22 | let operation = GetSubtitlesOperation(completionHandler: completionHandler)
23 |
24 | self.operationQueue.addOperation(operation)
25 | }
26 |
27 | func getLinks(for types: [SessionDataTypes], wwdcYear: WWDC, sessionNumber: String? = nil, copyToUserDestinationURL: Bool, completionHandler: @escaping () -> Void) {
28 |
29 | let getLinksOperation = GetLinksOperation(for: types, wwdcYear: wwdcYear, sessionNumber: sessionNumber, copyToUserDestinationURL: copyToUserDestinationURL, completionHandler: completionHandler)
30 |
31 | self.operationQueue.addOperation(getLinksOperation)
32 |
33 | }
34 |
35 | func getSessionsList(for wwdcYear: WWDC, copyToUserDestinationURL: Bool, completionHandler: @escaping () -> Void) {
36 |
37 | let getSessionsListOperation = GetSessionsListOperation(for: wwdcYear, copyToUserDestinationURL: copyToUserDestinationURL, completionHandler: completionHandler)
38 |
39 | self.operationQueue.addOperation(getSessionsListOperation)
40 | }
41 |
42 | }
43 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Resources/WWDC2013_links.txt:
--------------------------------------------------------------------------------
1 | http://devstreaming.apple.com/videos/wwdc/2013/710xfx3xn8197k4i9s2rvyb/710/710-HD.mov?dl=1
2 | http://devstreaming.apple.com/videos/wwdc/2013/202xdx2x47ezp1wein/202/202-HD.mov?dl=1
3 | http://devstreaming.apple.com/videos/wwdc/2013/200xdx2x35e1pxiinm/200/200-HD.mov?dl=1
4 | http://devstreaming.apple.com/videos/wwdc/2013/413xdx5x97itb5ek4yex3r7/413/413-HD.mov?dl=1
5 | http://devstreaming.apple.com/videos/wwdc/2013/612xax4xx65z1ervy5np1qb/612/612-HD.mov?dl=1
6 | http://devstreaming.apple.com/videos/wwdc/2013/221xex4xxohbllf4hblyngt/221/221-HD.mov?dl=1
7 | http://devstreaming.apple.com/videos/wwdc/2013/220xbx4xipaxfd1tggxuoib/220/220-HD.mov?dl=1
8 | http://devstreaming.apple.com/videos/wwdc/2013/711xcx4x8yuutk8sady6t9f/711/711-HD.mov?dl=1
9 | http://devstreaming.apple.com/videos/wwdc/2013/404xbx2xvp1eaaqonr8zokm/404/404-HD.mov?dl=1
10 | http://devstreaming.apple.com/videos/wwdc/2013/505xbx4xrgmhwby4oiwkrpp/505/505-HD.mov?dl=1
11 | http://devstreaming.apple.com/videos/wwdc/2013/102xex1x2e4xpykz1y/102/102-HD.mov?dl=1
12 | http://devstreaming.apple.com/videos/wwdc/2013/213xax3xr33a8oxfsxg8bha/213/213-HD.mov?dl=1
13 | http://devstreaming.apple.com/videos/wwdc/2013/225xex5xkk51ehr0z54gd9kt/225/225-HD.mov?dl=1
14 | http://devstreaming.apple.com/videos/wwdc/2013/216xcx4x7if809qdggi7vcc/216/216-HD.mov?dl=1
15 | http://devstreaming.apple.com/videos/wwdc/2013/611xfx3x2l2zzkb2oaovson/611/611-HD.mov?dl=1
16 | http://devstreaming.apple.com/videos/wwdc/2013/704xcx2xrmodatdtgs6nf5w4/704/704-HD.mov?dl=1
17 | http://devstreaming.apple.com/videos/wwdc/2013/201xex2xxf5ynwnsgl/201/201-HD.mov?dl=1
18 | http://devstreaming.apple.com/videos/wwdc/2013/412xdx4x6xpjownhu5d4as36/412/412-HD.mov?dl=1
19 | http://devstreaming.apple.com/videos/wwdc/2013/703xcx2xljtlq481grxurla4/703/703-HD.mov?dl=1
20 | http://devstreaming.apple.com/videos/wwdc/2013/211xcx3xeognrgah6anssirb/211/211-HD.mov?dl=1
21 | http://devstreaming.apple.com/videos/wwdc/2013/509xbx5xydkscnk7eyuyj1qi/509/509-HD.mov?dl=1
22 | http://devstreaming.apple.com/videos/wwdc/2013/218xdx4xj2umojkv3e8dbk6i/218/218-HD.mov?dl=1
23 | http://devstreaming.apple.com/videos/wwdc/2013/214xex3xkbq0z93doa6o31c0/214/214-HD.mov?dl=1
24 | http://devstreaming.apple.com/videos/wwdc/2013/407xdx3xw3kl5xx1h5cs73sp/407/407-HD.mov?dl=1
25 | http://devstreaming.apple.com/videos/wwdc/2013/700xcx2xuedgs1rni4qry1yumg/700/700-HD.mov?dl=1
26 | http://devstreaming.apple.com/videos/wwdc/2013/224xcx5x1y1yx8ra5jbmfyhf/224/224-HD.mov?dl=1
27 | http://devstreaming.apple.com/videos/wwdc/2013/503xbx3xoaktjug4e05xvl04/503/503-HD.mov?dl=1
28 | http://devstreaming.apple.com/videos/wwdc/2013/702xfx2xmlrics5pyrjfwu2m/702/702-HD.mov?dl=1
29 | http://devstreaming.apple.com/videos/wwdc/2013/712xax4xb4mh6prvqkesmm75/712/712-HD.mov?dl=1
30 | http://devstreaming.apple.com/videos/wwdc/2013/217xex4xi4ws3f5wrv3w2wkh/217/217-HD.mov?dl=1
31 | http://devstreaming.apple.com/videos/wwdc/2013/301xcx2xzxf8qjdcu3y2k1itm/301/301-HD.mov?dl=1
32 | http://devstreaming.apple.com/videos/wwdc/2013/410xcx4x9x0ry3zbw3rzq14kx/410/410-HD.mov?dl=1
33 | http://devstreaming.apple.com/videos/wwdc/2013/403xex2xoo3u74gemi4q9vhvs/403/403-HD.mov?dl=1
34 | http://devstreaming.apple.com/videos/wwdc/2013/206xex2xws29m12p69m7s169q/206/206-HD.mov?dl=1
35 | http://devstreaming.apple.com/videos/wwdc/2013/603xfx2xhszvvn0zgc1cbgx1j/603/603-HD.mov?dl=1
36 | http://devstreaming.apple.com/videos/wwdc/2013/601xfx2xk7wrmhwsa5gao39ln/601/601-HD.mov?dl=1
37 | http://devstreaming.apple.com/videos/wwdc/2013/310xex5xg29dwip7fgthel9z2/310/310-HD.mov?dl=1
38 | http://devstreaming.apple.com/videos/wwdc/2013/228xax5xif2s7s53df5ffjfao2/228/228-HD.mov?dl=1
39 | http://devstreaming.apple.com/videos/wwdc/2013/613xfx4xp91m26w7iu790d3nx/613/613-HD.mov?dl=1
40 | http://devstreaming.apple.com/videos/wwdc/2013/226xbx5xinmlvbdabxux9k3kt/226/226-HD.mov?dl=1
41 | http://devstreaming.apple.com/videos/wwdc/2013/614xax5x602jmihlq4c4edtrl/614/614-HD.mov?dl=1
42 | http://devstreaming.apple.com/videos/wwdc/2013/209xdx3xahizhph6dg8wk631i/209/209-HD.mov?dl=1
43 | http://devstreaming.apple.com/videos/wwdc/2013/615xax5xpcdns8jyhaiszkz2p/615/615-HD.mov?dl=1
44 | http://devstreaming.apple.com/videos/wwdc/2013/303xdx3xodi2951d3kopnrhlg/303/303-HD.mov?dl=1
45 | http://devstreaming.apple.com/videos/wwdc/2013/501xdx2xat8ocml4nv7i08noe/501/501-HD.mov?dl=1
46 | http://devstreaming.apple.com/videos/wwdc/2013/405xcx3x5ud6sopkxfqg7ikk/405/405-HD.mov?dl=1
47 | http://devstreaming.apple.com/videos/wwdc/2013/416xxx3x90yhsjxfqfbi/416/416-HD.mov?dl=1
48 | http://devstreaming.apple.com/videos/wwdc/2013/604xfx2xl2ujhx7vffbjmtqu2t/604/604-HD.mov?dl=1
49 | http://devstreaming.apple.com/videos/wwdc/2013/210xbx3xpg6jhh8okmc7zc8j89/210/210-HD.mov?dl=1
50 | http://devstreaming.apple.com/videos/wwdc/2013/609xfx3xpwcil5bz8h6ot4f871/609/609-HD.mov?dl=1
51 | http://devstreaming.apple.com/videos/wwdc/2013/502xex3x2iwfiaeglpjw0mh54u/502/502-HD.mov?dl=1
52 | http://devstreaming.apple.com/videos/wwdc/2013/100xex1xb4fuo9xiu0/100/100-HD.mov?dl=1
53 | http://devstreaming.apple.com/videos/wwdc/2013/219xax4xjor8i6b9h77lafay32/219/219-HD.mov?dl=1
54 | http://devstreaming.apple.com/videos/wwdc/2013/300xdx2xem8o4pmrhvraq9ty76/300/300-HD.mov?dl=1
55 | http://devstreaming.apple.com/videos/wwdc/2013/415xdx5x0wygxidaf0ifiey/415/415-HD.mov?dl=1
56 | http://devstreaming.apple.com/videos/wwdc/2013/701xbx2xqblo39z6tpbdrcz/701/701-HD.mov?dl=1
57 | http://devstreaming.apple.com/videos/wwdc/2013/606xdx2xbp31zp28fdov8p0b6d/606/606-HD.mov?dl=1
58 | http://devstreaming.apple.com/videos/wwdc/2013/708xbx3x7xusbzidl0j3acxest/708/708-HD.mov?dl=1
59 | http://devstreaming.apple.com/videos/wwdc/2013/408xcx3x0l4e2phvin8xrhsn23/408/408-HD.mov?dl=1
60 | http://devstreaming.apple.com/videos/wwdc/2013/215xax3xz5pbbxeaxxe7z1mk3q/215/215-HD.mov?dl=1
61 | http://devstreaming.apple.com/videos/wwdc/2013/417xxx0oh4r99eed2hb1k3ce/417/417-HD.mov?dl=1
62 | http://devstreaming.apple.com/videos/wwdc/2013/109xxxjfr9zgakbgrzxk23n2/109/109-HD.mov?dl=1
63 | http://devstreaming.apple.com/videos/wwdc/2013/101xex1x82z7rpszsw/101/101-HD.mov?dl=1
64 | http://devstreaming.apple.com/videos/wwdc/2013/607xfx3xox1jscc7z24fl2jf4mf/607/607-HD.mov?dl=1
65 | http://devstreaming.apple.com/videos/wwdc/2013/608xcx3xafjdvmnjpo5dzvou97r/608/608-HD.mov?dl=1
66 | http://devstreaming.apple.com/videos/wwdc/2013/709xax3xiafkagts5jfa5705dx6/709/709-HD.mov?dl=1
67 | http://devstreaming.apple.com/videos/wwdc/2013/714xbx5xcen10zkjl5f9sd8ys63/714/714-HD.mov?dl=1
68 | http://devstreaming.apple.com/videos/wwdc/2013/309xdx4x891fj1ing58e5cayt1z/309/309-HD.mov?dl=1
69 | http://devstreaming.apple.com/videos/wwdc/2013/227xax5xif2s7s531dsmfs1afo2/227/227-HD.mov?dl=1
70 | http://devstreaming.apple.com/videos/wwdc/2013/406xex3x90a7ka0kyhsfjxfqfbi/406/406-HD.mov?dl=1
71 | http://devstreaming.apple.com/videos/wwdc/2013/409xdx4x4adcwca5ok0rtsup0sg/409/409-HD.mov?dl=1
72 | http://devstreaming.apple.com/videos/wwdc/2013/713xcx4xtaue02i1tvk0kpvarvo/713/713-HD.mov?dl=1
73 | http://devstreaming.apple.com/videos/wwdc/2013/506xbx4x8brixcxa41wrzgph0gw/506/506-HD.mov?dl=1
74 | http://devstreaming.apple.com/videos/wwdc/2013/414xdx5xbjc8ls04ewrox0a160i/414/414-HD.mov?dl=1
75 | http://devstreaming.apple.com/videos/wwdc/2013/223xex5xsgdfh1ergtjrqwoghbj/223/223-HD.mov?dl=1
76 | http://devstreaming.apple.com/videos/wwdc/2013/308xex4x6ybggtlw4ztv0sg5btp/308/308-HD.mov?dl=1
77 | http://devstreaming.apple.com/videos/wwdc/2013/305xbx4xqongltzvuja8xrmsrq1/305/305-HD.mov?dl=1
78 | http://devstreaming.apple.com/videos/wwdc/2013/610xcx3xv9xjy916g3wzes0ze63/610/610-HD.mov?dl=1
79 | http://devstreaming.apple.com/videos/wwdc/2013/203xex2xro3o27pyntvhsqsohil/203/203-HD.mov?dl=1
80 | http://devstreaming.apple.com/videos/wwdc/2013/205xbx2xfbtmyu1l18h36mfqxmy/205/205-HD.mov?dl=1
81 | http://devstreaming.apple.com/videos/wwdc/2013/602xcx2xk6ipx0cusjryu1sx5eu/602/602-HD.mov?dl=1
82 | http://devstreaming.apple.com/videos/wwdc/2013/207xdx3xbarjw2d2va5olp57qh8/207/207-HD.mov?dl=1
83 | http://devstreaming.apple.com/videos/wwdc/2013/307xex4xl1ey243ksyxqfip0xowr/307/307-HD.mov?dl=1
84 | http://devstreaming.apple.com/videos/wwdc/2013/705xbx3xcjsmrdbtwl5grta6gq6r/705/705-HD.mov?dl=1
85 | http://devstreaming.apple.com/videos/wwdc/2013/504xbx3x55lc470bv6s8dk2lcg28/504/504-HD.mov?dl=1
86 | http://devstreaming.apple.com/videos/wwdc/2013/605xfx2xbot31cme1uns8w647vsw/605/605-HD.mov?dl=1
87 | http://devstreaming.apple.com/videos/wwdc/2013/208xex3xyxmpz8s37fk59avul0c5/208/208-HD.mov?dl=1
88 | http://devstreaming.apple.com/videos/wwdc/2013/306xdx4xq2n1jhue4dfou0nemckj/306/306-HD.mov?dl=1
89 | http://devstreaming.apple.com/videos/wwdc/2013/707xfx3xysb4yyrvtxbbu6t9fono/707/707-HD.mov?dl=1
90 | http://devstreaming.apple.com/videos/wwdc/2013/304xex4x7qun15qmlzgf1s39fn08/304/304-HD.mov?dl=1
91 | http://devstreaming.apple.com/videos/wwdc/2013/507xax4xgxp9uuxl9i7y0emdv5ib/507/507-HD.mov?dl=1
92 | http://devstreaming.apple.com/videos/wwdc/2013/302xdx3xf65k8c0kkajsjy2mh9jj/302/302-HD.mov?dl=1
93 | http://devstreaming.apple.com/videos/wwdc/2013/600xbx2x3eaj3ryz3w5zw5gozz39/600/600-HD.mov?dl=1
94 | http://devstreaming.apple.com/videos/wwdc/2013/500xbx2xh3dv43mpm46sd0qzj8d0/500/500-HD.mov?dl=1
95 | http://devstreaming.apple.com/videos/wwdc/2013/222xbx4xitmr47hmc2ulz1tli4hv/222/222-HD.mov?dl=1
96 | http://devstreaming.apple.com/videos/wwdc/2013/402xdx2x3ccrfzk85j9dysimvsui/402/402-HD.mov?dl=1
97 | http://devstreaming.apple.com/videos/wwdc/2013/400xex2xbskwa5bkxr17zihju9uf/400/400-HD.mov?dl=1
98 | http://devstreaming.apple.com/videos/wwdc/2013/204xex2xvpdncz9kdb17lmfooh/204/204-HD.mov?dl=1
99 | http://devstreaming.apple.com/videos/wwdc/2013/508xax4xqkee4rd9rsmbb86dw5cu/508/508-HD.mov?dl=1
100 | http://devstreaming.apple.com/videos/wwdc/2013/401xbx2xq50gv7mqafyfdns2yhxl/401/401-HD.mov?dl=1
101 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Resources/WWDC2014_links.txt:
--------------------------------------------------------------------------------
1 | http://devstreaming.apple.com/videos/wwdc/2014/403xxksrj0qs8c0/403/403_hd_intermediate_swift.mov?dl=1
2 | http://devstreaming.apple.com/videos/wwdc/2014/419xxli6f60a6bs/419/419_hd_advanced_graphics_and_animation_performance.mov?dl=1
3 | http://devstreaming.apple.com/videos/wwdc/2014/101xx36lr6smzjo/101/101_hd.mov?dl=1
4 | http://devstreaming.apple.com/videos/wwdc/2014/236xxwk3fv82sx2/236/236_hd_building_interruptible_and_responsive_interactions.mov?dl=1
5 | http://devstreaming.apple.com/videos/wwdc/2014/306xxjtg7uz13v0/306/306_hd_javascript_for_automation.mov?dl=1
6 | http://devstreaming.apple.com/videos/wwdc/2014/404xxdxsstkaqjb/404/404_hd_advanced_swift.mov?dl=1
7 | http://devstreaming.apple.com/videos/wwdc/2014/701xx8n8ca3aq4j/701/701_hd_designing_accessories_for_ios_and_os_x.mov?dl=1
8 | http://devstreaming.apple.com/videos/wwdc/2014/224xxxlsvigdoc0/224/224_hd_core_os_ios_application_architectural_patterns.mov?dl=1
9 | http://devstreaming.apple.com/videos/wwdc/2014/717xxux5eg6f9v4/717/717_hd_kids_and_apps.mov?dl=1
10 | http://devstreaming.apple.com/videos/wwdc/2014/716xx8q4shlqcp8/716/716_hd_power_performance_and_diagnostics_whats_new_in_gcd_and_xpc_for.mov?dl=1
11 | http://devstreaming.apple.com/videos/wwdc/2014/712xx1pl2u942g2/712/712_hd_writing_energy_efficient_code_part_2.mov?dl=1
12 | http://devstreaming.apple.com/videos/wwdc/2014/710xxwwk9jiqtu2/710/710_hd_writing_energy_efficient_code_part_1.mov?dl=1
13 | http://devstreaming.apple.com/videos/wwdc/2014/713xx1il4h4ur9c/713/713_hd_whats_new_in_ios_notifications.mov?dl=1
14 | http://devstreaming.apple.com/videos/wwdc/2014/705xx0r0x0fsaf5/705/705_hd_distributing_enterprise_apps.mov?dl=1
15 | http://devstreaming.apple.com/videos/wwdc/2014/704xx7dmqd5m9l4/704/704_hd_building_apps_for_enterprise_and_education.mov?dl=1
16 | http://devstreaming.apple.com/videos/wwdc/2014/702xxvsjwkmhw2e/702/702_hd_managing_apple_devices.mov?dl=1
17 | http://devstreaming.apple.com/videos/wwdc/2014/408xxcm26svis12/408/408_hd_swift_playgrounds.mov?dl=1
18 | http://devstreaming.apple.com/videos/wwdc/2014/410xx1s19e83i5z/410/410_hd_advanced_swift_debugging_in_lldb.mov?dl=1
19 | http://devstreaming.apple.com/videos/wwdc/2014/407xxptt888z5jv/407/407_hd_swift_interoperability_in_depth.mov?dl=1
20 | http://devstreaming.apple.com/videos/wwdc/2014/216xxcnxc6wnkf3/216/216_hd_building_adaptive_apps_with_uikit.mov?dl=1
21 | http://devstreaming.apple.com/videos/wwdc/2014/226xxf6phq7ufzl/226/226_hd_whats_new_in_table_and_collection_views.mov?dl=1
22 | http://devstreaming.apple.com/videos/wwdc/2014/209xxbyg01mfqt8/209/209_hd_adapting_your_app_to_the_new_ui_of_os_x_yosemite.mov?dl=1
23 | http://devstreaming.apple.com/videos/wwdc/2014/220xx01yweszmjv/220/220_hd_adopting_advanced_features_of_the_new_ui_of_os_x_yosemite.mov?dl=1
24 | http://devstreaming.apple.com/videos/wwdc/2014/212xxi1kzzkdr54/212/212_hd_storyboards_and_controllers_on_os_x.mov?dl=1
25 | http://devstreaming.apple.com/videos/wwdc/2014/204xxhe1lli87dm/204/204_hd_whats_new_in_cocoa.mov?dl=1
26 | http://devstreaming.apple.com/videos/wwdc/2014/235xxsugqo8pxak/235/235_hd_advanced_scrollviews_and_touch_handling_techniques.mov?dl=1
27 | http://devstreaming.apple.com/videos/wwdc/2014/214xxq2mdbtmp23/214/214_hd_view_controller_advancements_in_ios_8.mov?dl=1
28 | http://devstreaming.apple.com/videos/wwdc/2014/237xxcyp7vhx2xt/237/237_hd_a_strategy_for_great_work.mov?dl=1
29 | http://devstreaming.apple.com/videos/wwdc/2014/228xxnfgueiskhi/228/228_hd_a_look_inside_presentation_controllers.mov?dl=1
30 | http://devstreaming.apple.com/videos/wwdc/2014/234xxi5cismq5hn/234/234_hd_building_a_document_based_app.mov?dl=1
31 | http://devstreaming.apple.com/videos/wwdc/2014/416xxuit620s53g/416/416_hd_building_modern_frameworks.mov?dl=1
32 | http://devstreaming.apple.com/videos/wwdc/2014/415xx83xkyr55fj/415/415_hd_continuous_integration_with_xcode_6.mov?dl=1
33 | http://devstreaming.apple.com/videos/wwdc/2014/414xx4l5du0f408/414/414_hd_testing_in_xcode_6.mov?dl=1
34 | http://devstreaming.apple.com/videos/wwdc/2014/413xxr7gdc60u2p/413/413_hd_debugging_in_xcode_6.mov?dl=1
35 | http://devstreaming.apple.com/videos/wwdc/2014/412xx80au1lrfcn/412/412_hd_localizing_with_xcode_6.mov?dl=1
36 | http://devstreaming.apple.com/videos/wwdc/2014/411xx0xo98zzoor/411/411_hd_whats_new_in_interface_builder.mov?dl=1
37 | http://devstreaming.apple.com/videos/wwdc/2014/401xxfkzfrjyb93/401/401_hd_whats_new_in_xcode_6.mov?dl=1
38 | http://devstreaming.apple.com/videos/wwdc/2014/301xxu0xo3hhg9h/301/301_hd_affiliate_tools_for_app_developers.mov?dl=1
39 | http://devstreaming.apple.com/videos/wwdc/2014/417xx2zsyyp8zcs/417/417_hd_whats_new_in_llvm.mov?dl=1
40 | http://devstreaming.apple.com/videos/wwdc/2014/406xxssvkspk997/406/406_hd_integrating_swift_with_objective_c.mov?dl=1
41 | http://devstreaming.apple.com/videos/wwdc/2014/402xxgg8o88ulsr/402/402_hd_introduction_to_swift.mov?dl=1
42 | http://devstreaming.apple.com/videos/wwdc/2014/409xxfw34pado34/409/409_hd_introduction_to_lldb_and_the_swift_repl.mov?dl=1
43 | http://devstreaming.apple.com/videos/wwdc/2014/418xxtihju1a7v4/418/418_hd_improving_your_app_with_instruments.mov?dl=1
44 | http://devstreaming.apple.com/videos/wwdc/2014/225xxgzhqylosff/225/225_hd_whats_new_in_core_data.mov?dl=1
45 | http://devstreaming.apple.com/videos/wwdc/2014/231xx9bil1zgee7/231/231_hd_advanced_cloudkit.mov?dl=1
46 | http://devstreaming.apple.com/videos/wwdc/2014/208xx42tf0hw3vv/208/208_hd_introducing_cloudkit.mov?dl=1
47 | http://devstreaming.apple.com/videos/wwdc/2014/233xxwktnowwj0u/233/233_hd_sharing_code_between_ios_and_os_x.mov?dl=1
48 | http://devstreaming.apple.com/videos/wwdc/2014/509xxwli42i4gs6/509/509_hd_creating_3d_interactive_content_with_webgl.mov?dl=1
49 | http://devstreaming.apple.com/videos/wwdc/2014/506xxeo80e5kykp/506/506_hd_ensuring_continuity_between_your_app_and_web_site.mov?dl=1
50 | http://devstreaming.apple.com/videos/wwdc/2014/512xxj53iuolu78/512/512_hd_web_inspector_and_modern_javascript.mov?dl=1
51 | http://devstreaming.apple.com/videos/wwdc/2014/517xxlpmjgeqazd/517/517_hd_designing_responsive_web_experiences.mov?dl=1
52 | http://devstreaming.apple.com/videos/wwdc/2014/206xxdiurnffagr/206/206_hd_introducing_the_modern_webkit_api.mov?dl=1
53 | http://devstreaming.apple.com/videos/wwdc/2014/504xx5n1n7eie65/504/504_hd_advanced_media_for_the_web.mov?dl=1
54 | http://devstreaming.apple.com/videos/wwdc/2014/516xxanja9ziaar/516/516_hd_improving_the_accessibility_and_usability_of_complex_web_applications.mov?dl=1
55 | http://devstreaming.apple.com/videos/wwdc/2014/510xx6yeo9go0lo/510/510_hd_whats_new_in_iad_workbench.mov?dl=1
56 | http://devstreaming.apple.com/videos/wwdc/2014/222xxz9991l36ro/222/222_hd_optimize_your_earning_power_with_iad.mov?dl=1
57 | http://devstreaming.apple.com/videos/wwdc/2014/718xxctf8ley20j/718/718_hd_adopting_airprint.mov?dl=1
58 | http://devstreaming.apple.com/videos/wwdc/2014/703xx9ich9e8mkn/703/703_hd_whats_new_in_the_accelerate_framework.mov?dl=1
59 | http://devstreaming.apple.com/videos/wwdc/2014/714xx1h4szxdnyz/714/714_hd_fix_bugs_faster_using_activity_tracing.mov?dl=1
60 | http://devstreaming.apple.com/videos/wwdc/2014/707xx1o5tdjnvg9/707/707_hd_whats_new_in_foundation_networking.mov?dl=1
61 | http://devstreaming.apple.com/videos/wwdc/2014/709xx1q8hdvo14x/709/709_hd_cross_platform_nearby_networking.mov?dl=1
62 | http://devstreaming.apple.com/videos/wwdc/2014/715xx4loqo5can9/715/715_hd_user_privacy_in_ios_and_os_x.mov?dl=1
63 | http://devstreaming.apple.com/videos/wwdc/2014/711xx6j5wzufu78/711/711_hd_keychain_and_authentication_with_touch_id.mov?dl=1
64 | http://devstreaming.apple.com/videos/wwdc/2014/708xxvfd08bdof2/708/708_hd_taking_core_location_indoors.mov?dl=1
65 | http://devstreaming.apple.com/videos/wwdc/2014/706xxjytntg51wd/706/706_hd_whats_new_in_core_location.mov?dl=1
66 | http://devstreaming.apple.com/videos/wwdc/2014/211xxmyz80g30i9/211/211_hd_designing_intuitive_user_experiences.mov?dl=1
67 | http://devstreaming.apple.com/videos/wwdc/2014/210xxksa9s9ewsa/210/210_hd_accessibility_on_ios.mov?dl=1
68 | http://devstreaming.apple.com/videos/wwdc/2014/207xx270npvffao/207/207_hd_accessibility_on_os_x.mov?dl=1
69 | http://devstreaming.apple.com/videos/wwdc/2014/202xx3ane09vxdz/202/202_hd_whats_new_in_cocoa_touch.mov?dl=1
70 | http://devstreaming.apple.com/videos/wwdc/2014/229xx77tq0pmkwo/229/229_hd_advanced_ios_architecture_and_patterns.mov?dl=1
71 | http://devstreaming.apple.com/videos/wwdc/2014/219xxebl6quoz05/219/219_hd_adopting_handoff_on_ios_and_os_x.mov?dl=1
72 | http://devstreaming.apple.com/videos/wwdc/2014/232xxz8gxpbstio/232/232_hd_advanced_user_interfaces_with_collection_views.mov?dl=1
73 | http://devstreaming.apple.com/videos/wwdc/2014/227xx1g0sa1mhjf/227/227_hd_creating_modern_cocoa_apps.mov?dl=1
74 | http://devstreaming.apple.com/videos/wwdc/2014/217xxsvxdga3rh5/217/217_hd_creating_extensions_for_ios_and_os_x_part_2.mov?dl=1
75 | http://devstreaming.apple.com/videos/wwdc/2014/205xxqzduadzo14/205/205_hd_creating_extensions_for_ios_and_os_x,_part_1.mov?dl=1
76 | http://devstreaming.apple.com/videos/wwdc/2014/221xxobzcm2j26x/221/221_hd_creating_custom_ios_user_interfaces.mov?dl=1
77 | http://devstreaming.apple.com/videos/wwdc/2014/201xx2xfazhzce8/201/201_hd_advanced_topics_in_internationalization.mov?dl=1
78 | http://devstreaming.apple.com/videos/wwdc/2014/223xxp1uag2jn3n/223/223_hd_prototyping_fake_it_till_you_make_it.mov?dl=1
79 | http://devstreaming.apple.com/videos/wwdc/2014/230xxe44dq1m2da/230/230_hd_making_a_great_first_impression_with_strong_onboarding_design.mov?dl=1
80 | http://devstreaming.apple.com/videos/wwdc/2014/218xx267rleu4n8/218/218_hd_designing_a_great_in-app_purchase_experience.mov?dl=1
81 | http://devstreaming.apple.com/videos/wwdc/2014/304xxc65wjxydj8/304/304_hd_creating_great_app_previews.mov?dl=1
82 | http://devstreaming.apple.com/videos/wwdc/2014/305xxjjl70ix0y1/305/305_hd_preventing_unauthorized_purchases_with_receipts.mov?dl=1
83 | http://devstreaming.apple.com/videos/wwdc/2014/303xxqw0yb14u0r/303/303_hd_optimizing_in-app_purchases.mov?dl=1
84 | http://devstreaming.apple.com/videos/wwdc/2014/302xxo8xxixuera/302/302_hd_the_new_itunes_connect.mov?dl=1
85 | http://devstreaming.apple.com/videos/wwdc/2014/511xxegx4yyhbt9/511/511_hd_introducing_the_photos_frameworks.mov?dl=1
86 | http://devstreaming.apple.com/videos/wwdc/2014/515xxv01d9tcg3o/515/515_hd_developing_core_image_filters_for_ios.mov?dl=1
87 | http://devstreaming.apple.com/videos/wwdc/2014/514xxio5buvlu16/514/514_hd_advances_in_core_image.mov?dl=1
88 | http://devstreaming.apple.com/videos/wwdc/2014/502xxvo7vov799k/502/502_hd_avaudioengine_in_practice.mov?dl=1
89 | http://devstreaming.apple.com/videos/wwdc/2014/501xxfo4d68h054/501/501_hd_whats_new_in_core_audio.mov?dl=1
90 | http://devstreaming.apple.com/videos/wwdc/2014/508xxfvaehrll14/508/508_hd_camera_capture_manual_controls.mov?dl=1
91 | http://devstreaming.apple.com/videos/wwdc/2014/513xxhfudagscto/513/513_hd_direct_access_to_media_encoding_and_decoding.mov?dl=1
92 | http://devstreaming.apple.com/videos/wwdc/2014/505xx5j7n7h3a1q/505/505_hd_harnessing_metadata_in_audiovisual_media.mov?dl=1
93 | http://devstreaming.apple.com/videos/wwdc/2014/503xx50xm4n63qe/503/503_hd_mastering_modern_media_playback.mov?dl=1
94 | http://devstreaming.apple.com/videos/wwdc/2014/602xx0qpmdkc2ki/602/602_hd_ingredients_of_great_games.mov?dl=1
95 | http://devstreaming.apple.com/videos/wwdc/2014/612xxnsoq5fis79/612/612_hd_motion_tracking_with_the_core_motion_framework.mov?dl=1
96 | http://devstreaming.apple.com/videos/wwdc/2014/601xxu3eg5ttkcj/601/601_hd_harnessing_the_power_of_the_mac_pro_with_opengl_and_opencl.mov?dl=1
97 | http://devstreaming.apple.com/videos/wwdc/2014/605xxygcz4pd0h6/605/605_hd_working_with_metal_advanced.mov?dl=1
98 | http://devstreaming.apple.com/videos/wwdc/2014/604xxg7crkljcr8/604/604_hd_working_with_metal_fundamentals.mov?dl=1
99 | http://devstreaming.apple.com/videos/wwdc/2014/603xx33n8igr5n1/603/603_hd_working_with_metal_overview.mov?dl=1
100 | http://devstreaming.apple.com/videos/wwdc/2014/610xxc04fgmv80x/610/610_hd_building_a_game_with_scenekit.mov?dl=1
101 | http://devstreaming.apple.com/videos/wwdc/2014/609xxkxq1v95fju/609/609_hd_whats_new_in_scenekit.mov?dl=1
102 | http://devstreaming.apple.com/videos/wwdc/2014/608xx0tzmkcqkrn/608/608_hd_best_practices_for_building_spritekit_games.mov?dl=1
103 | http://devstreaming.apple.com/videos/wwdc/2014/606xxql3qoibema/606/606_hd_whats_new_in_sprite_kit.mov?dl=1
104 | http://devstreaming.apple.com/videos/wwdc/2014/611xxblieag1z2a/611/611_hd_designing_for_game_controllers.mov?dl=1
105 | http://devstreaming.apple.com/videos/wwdc/2014/203xxh9oqtm0piw/203/203_hd_introducing_healthkit.mov?dl=1
106 | http://devstreaming.apple.com/videos/wwdc/2014/213xxbmca4in9u5/213/213_hd_introducing_homekit.mov?dl=1
107 | http://devstreaming.apple.com/videos/wwdc/2014/103xx8s53gk94hl/103/103_hd_apple_design_awards.mov?dl=1
108 | http://devstreaming.apple.com/videos/wwdc/2014/102xxw2o82y78a4/102/102_hd_platforms_state_of_the_union.mov?dl=1
--------------------------------------------------------------------------------
/WWDCSubGetter/Resources/WWDC2015_links.txt:
--------------------------------------------------------------------------------
1 | http://devstreaming.apple.com/videos/wwdc/2015/217wu453thu1r1/217/217_hd_adopting_new_trackpad_features.mp4?dl=1
2 | http://devstreaming.apple.com/videos/wwdc/2015/2267p2ni281ba/226/226_hd_advanced_nsoperations.mp4?dl=1
3 | http://devstreaming.apple.com/videos/wwdc/2015/233l9q8hj9mw/233/233_hd_advanced_touch_input_on_ios.mp4?dl=1
4 | http://devstreaming.apple.com/videos/wwdc/2015/224o6pqmtb4ik/224/224_hd_app_extension_best_practices.mp4?dl=1
5 | http://devstreaming.apple.com/videos/wwdc/2015/2048w4vdjhe1i1m/204/204_hd_apple_watch_accessibility.mp4?dl=1
6 | http://devstreaming.apple.com/videos/wwdc/2015/232f1zopzycv/232/232_hd_best_practices_for_progress_reporting.mp4?dl=1
7 | http://devstreaming.apple.com/videos/wwdc/2015/213w6grumlfm0q/213/213_hd_building_apps_with_researchkit.mp4?dl=1
8 | http://devstreaming.apple.com/videos/wwdc/2015/234reaz1byqc/234/234_hd_building_document_based_apps.mp4?dl=1
9 | http://devstreaming.apple.com/videos/wwdc/2015/2313dt427pmq/231/231_hd_cocoa_touch_best_practices.mp4?dl=1
10 | http://devstreaming.apple.com/videos/wwdc/2015/209c9277tttlt9/209/209_hd_creating_complications_with_clockkit.mp4?dl=1
11 | http://devstreaming.apple.com/videos/wwdc/2015/2058z3fx76huw93/205/205_hd_getting_started_with_multitasking_on_ipad_in_ios_9.mp4?dl=1
12 | http://devstreaming.apple.com/videos/wwdc/2015/22160ycymy0qg/221/221_hd_improving_the_full_screen_window_experience.mp4?dl=1
13 | http://devstreaming.apple.com/videos/wwdc/2015/214dh5q5d0kswh/214/214_hd_introducing_on_demand_resources.mp4?dl=1
14 | http://devstreaming.apple.com/videos/wwdc/2015/5048tyhotl6/504/504_hd_introducing_safari_view_controller.mp4?dl=1
15 | http://devstreaming.apple.com/videos/wwdc/2015/223rmo6dv9hxh/223/223_hd_introducing_the_contacts_framework_for_ios_and_os_x.mp4?dl=1
16 | http://devstreaming.apple.com/videos/wwdc/2015/216isrjt4ku9w4/216/216_hd_layout_and_animation_techniques_for_watchkit.mp4?dl=1
17 | http://devstreaming.apple.com/videos/wwdc/2015/211dseo3cn0bnw/211/211_hd_multitasking_essentials_for_mediabased_apps_on_ipad_in_ios_9.mp4?dl=1
18 | http://devstreaming.apple.com/videos/wwdc/2015/2187le7kpyhdff/218/218_hd_mysteries_of_auto_layout_part_1.mp4?dl=1
19 | http://devstreaming.apple.com/videos/wwdc/2015/219u3bqgvsz2g/219/219_hd_mysteries_of_auto_layout_part_2.mp4?dl=1
20 | http://devstreaming.apple.com/videos/wwdc/2015/222ngkqh58b52/222/222_hd_new_uikit_support_for_international_user_interfaces.mp4?dl=1
21 | http://devstreaming.apple.com/videos/wwdc/2015/212mm5ra3oau66/212/212_hd_optimizing_your_app_for_multitasking_on_ipad_in_ios_9.mp4?dl=1
22 | http://devstreaming.apple.com/videos/wwdc/2015/230wt8hs0wt8/230/230_hd_performance_on_ios_and_watchos.mp4?dl=1
23 | http://devstreaming.apple.com/videos/wwdc/2015/5091mxk00t/509/509_hd_seamless_linking_to_your_app.mp4?dl=1
24 | http://devstreaming.apple.com/videos/wwdc/2015/207id8oiaxrt6lh/207/207_hd_watchkit_indepth_part_1.mp4?dl=1
25 | http://devstreaming.apple.com/videos/wwdc/2015/208wc2mdvock1md/208/208_hd_watchkit_indepth_part_2.mp4?dl=1
26 | http://devstreaming.apple.com/videos/wwdc/2015/228eahxjbaops/228/228_hd_watchkit_tips_and_tricks.mp4?dl=1
27 | http://devstreaming.apple.com/videos/wwdc/2015/2023wpov1sxpnf9/202/202_hd_whats_new_in_cocoa.mp4?dl=1
28 | http://devstreaming.apple.com/videos/wwdc/2015/220lgx5lvphj2/220/220_hd_whats_new_in_core_data.mp4?dl=1
29 | http://devstreaming.apple.com/videos/wwdc/2015/203bxvbtrom9t1t/203/203_hd_whats_new_in_healthkit.mp4?dl=1
30 | http://devstreaming.apple.com/videos/wwdc/2015/210oyq5peqlavb/210/210_hd_whats_new_in_homekit.mp4?dl=1
31 | http://devstreaming.apple.com/videos/wwdc/2015/227s0ti458qgg/227/227_hd_whats_new_in_internationalization.mp4?dl=1
32 | http://devstreaming.apple.com/videos/wwdc/2015/206v5ce46maax7s/206/206_hd_whats_new_in_mapkit.mp4?dl=1
33 | http://devstreaming.apple.com/videos/wwdc/2015/225629tzulwe0/225/225_hd_whats_new_in_nscollectionview.mp4?dl=1
34 | http://devstreaming.apple.com/videos/wwdc/2015/215972d0hjjcfx/215/215_hd_whats_new_in_storyboards.mp4?dl=1
35 | http://devstreaming.apple.com/videos/wwdc/2015/229fksrj39nd/229/229_hd_whats_new_in_uikit_dynamics_and_visual_effects.mp4?dl=1
36 | http://devstreaming.apple.com/videos/wwdc/2015/201pps6n6g0nsnz/201/201_hd_ios_accessibility.mp4?dl=1
37 | http://devstreaming.apple.com/videos/wwdc/2015/805yjy11epjkgmnn11/805/805_hd_apple_watch_design_tips_and_tricks.mp4?dl=1
38 | http://devstreaming.apple.com/videos/wwdc/2015/802mpzd3nzovlygpbg/802/802_hd_designing_for_apple_watch.mp4?dl=1
39 | http://devstreaming.apple.com/videos/wwdc/2015/801auxyvb1pgtkufjk/801/801_hd_designing_for_future_hardware.mp4?dl=1
40 | http://devstreaming.apple.com/videos/wwdc/2015/803q4kw6eqia7ssp17/803/803_hd_designing_with_animation.mp4?dl=1
41 | http://devstreaming.apple.com/videos/wwdc/2015/804eub264zh3x4j9yb/804/804_hd_introducing_the_new_system_fonts.mp4?dl=1
42 | http://devstreaming.apple.com/videos/wwdc/2015/413eflf3lrh1tyo/413/413_hd_advanced_debugging_and_the_address_sanitizer.mp4?dl=1
43 | http://devstreaming.apple.com/videos/wwdc/2015/404l17yio30l549x/404/404_hd_app_thinning_in_xcode.mp4?dl=1
44 | http://devstreaming.apple.com/videos/wwdc/2015/405t69ymgwkmfzc9/405/405_hd_authoring_rich_playgrounds.mp4?dl=1
45 | http://devstreaming.apple.com/videos/wwdc/2015/414sklk5h2k3ki3/414/414_hd_building_better_apps_with_value_types_in_swift.mp4?dl=1
46 | http://devstreaming.apple.com/videos/wwdc/2015/41097fby32x3opk/410/410_hd_continuous_integration_and_code_coverage_in_xcode.mp4?dl=1
47 | http://devstreaming.apple.com/videos/wwdc/2015/4072909wwb9o9j3/407/407_hd_implementing_ui_designs_in_interface_builder.mp4?dl=1
48 | http://devstreaming.apple.com/videos/wwdc/2015/403l7ohdidhmnkgx/403/403_hd_improving_your_existing_apps_with_swift.mp4?dl=1
49 | http://devstreaming.apple.com/videos/wwdc/2015/4097c25o0qhs6g5/409/409_hd_optimizing_swift_performance.mp4?dl=1
50 | http://devstreaming.apple.com/videos/wwdc/2015/412rhea5amj6iaf/412/412_hd_profiling_in_depth.mp4?dl=1
51 | http://devstreaming.apple.com/videos/wwdc/2015/408509vyudbqvts/408/408_hd_protocoloriented_programming_in_swift.mp4?dl=1
52 | http://devstreaming.apple.com/videos/wwdc/2015/401gee20yy5v2men/401/401_hd_swift_and_objectivec_interoperability.mp4?dl=1
53 | http://devstreaming.apple.com/videos/wwdc/2015/4119flfsnsgmlfy/411/411_hd_swift_in_practice.mp4?dl=1
54 | http://devstreaming.apple.com/videos/wwdc/2015/406o0doszwo8r15m/406/406_hd_ui_testing_in_xcode.mp4?dl=1
55 | http://devstreaming.apple.com/videos/wwdc/2015/402c0mhxbs8tt52t/402/402_hd_whats_new_in_lldb.mp4?dl=1
56 | http://devstreaming.apple.com/videos/wwdc/2015/303qzm09e7/303/303_hd_getting_the_most_out_of_app_analytics.mp4?dl=1
57 | http://devstreaming.apple.com/videos/wwdc/2015/306vjwcqnm/306/306_hd_supporting_the_enterprise_with_os_x_automation.mp4?dl=1
58 | http://devstreaming.apple.com/videos/wwdc/2015/301tcfp66f/301/301_hd_whats_new_in_managing_apple_devices.mp4?dl=1
59 | http://devstreaming.apple.com/videos/wwdc/2015/302sxabxp0/302/302_hd_whats_new_in_itunes_connect.mp4?dl=1
60 | http://devstreaming.apple.com/videos/wwdc/2015/304ywrr62d/304/304_hd_itunes_connect_development_to_distribution.mp4?dl=1
61 | http://devstreaming.apple.com/videos/wwdc/2015/103ot7lzkdri2fvn1iyh/103/103_hd_apple_design_awards.mp4?dl=1
62 | http://devstreaming.apple.com/videos/wwdc/2015/1086gvs7f4vryixs49s6/108/108_hd_building_watch_apps.mp4?dl=1
63 | http://devstreaming.apple.com/videos/wwdc/2015/105ncyldc6ofunvsgtan/105/105_hd_introducing_watchkit_for_watchos_2.mp4?dl=1
64 | http://devstreaming.apple.com/videos/wwdc/2015/1014o78qhj07pbfxt9g7/101/101_hd_keynote.mp4?dl=1
65 | http://devstreaming.apple.com/videos/wwdc/2015/1026npwuy2crj2xyuq11/102/102_hd_platforms_state_of_the_union.mp4?dl=1
66 | http://devstreaming.apple.com/videos/wwdc/2015/1075hpxmc54818sn59su/107/107_hd_whats_new_in_cocoa_touch.mp4?dl=1
67 | http://devstreaming.apple.com/videos/wwdc/2015/106z3yjwpfymnauri96m/106/106_hd_whats_new_in_swift.mp4?dl=1
68 | http://devstreaming.apple.com/videos/wwdc/2015/104usewvb5m0qbwafx8p/104/104_hd_whats_new_in_xcode.mp4?dl=1
69 | http://devstreaming.apple.com/videos/wwdc/2015/609pzlyunriyjupp/609/609_hd_deeper_into_gameplaykit_with_demobots.mp4?dl=1
70 | http://devstreaming.apple.com/videos/wwdc/2015/606ui2ppsvalj4nn/606/606_hd_enhancements_to_scenekit.mp4?dl=1
71 | http://devstreaming.apple.com/videos/wwdc/2015/6053hq2fz0ebo0lm/605/605_hd_going_social_with_replaykit_and_game_center.mp4?dl=1
72 | http://devstreaming.apple.com/videos/wwdc/2015/608rpwq1ltvg5nmk/608/608_hd_introducing_gameplaykit.mp4?dl=1
73 | http://devstreaming.apple.com/videos/wwdc/2015/602868pb0ow6idb3w/602/602_hd_managing_3d_assets_with_model_io.mp4?dl=1
74 | http://devstreaming.apple.com/videos/wwdc/2015/610kn68riy9ms89m/610/610_hd_metal_performance_optimization_techniques.mp4?dl=1
75 | http://devstreaming.apple.com/videos/wwdc/2015/6037pi9rxl6tfss8w/603/603_hd_whats_new_in_metal_part_1.mp4?dl=1
76 | http://devstreaming.apple.com/videos/wwdc/2015/607g5z16fpl7pzgi/607/607_hd_whats_new_in_metal_part_2.mp4?dl=1
77 | http://devstreaming.apple.com/videos/wwdc/2015/604gq12qghmv39znb/604/604_hd_whats_new_in_spritekit.mp4?dl=1
78 | http://devstreaming.apple.com/videos/wwdc/2015/508691kyzp/508/508_hd_audio_unit_extensions.mp4?dl=1
79 | http://devstreaming.apple.com/videos/wwdc/2015/502sufwcpog/502/502_hd_content_protection_for_http_live_streaming.mp4?dl=1
80 | http://devstreaming.apple.com/videos/wwdc/2015/5062qehwhs/506/506_hd_editing_movies_in_av_foundation.mp4?dl=1
81 | http://devstreaming.apple.com/videos/wwdc/2015/503oad8l55m/503/503_hd_monetize_and_promote_your_app_with_iad.mp4?dl=1
82 | http://devstreaming.apple.com/videos/wwdc/2015/511kmynuza/511/511_hd_safari_extensibility_content_blocking_and_shared_links.mp4?dl=1
83 | http://devstreaming.apple.com/videos/wwdc/2015/5059xl75l59/505/505_hd_using_safari_to_deliver_and_debug_a_responsive_web_design.mp4?dl=1
84 | http://devstreaming.apple.com/videos/wwdc/2015/507pq8rldk/507/507_hd_whats_new_in_core_audio.mp4?dl=1
85 | http://devstreaming.apple.com/videos/wwdc/2015/510jiccqsz/510/510_hd_whats_new_in_core_image.mp4?dl=1
86 | http://devstreaming.apple.com/videos/wwdc/2015/501g8vwlgg2/501/501_hd_whats_new_in_web_development_in_webkit_and_safari.mp4?dl=1
87 | http://devstreaming.apple.com/videos/wwdc/2015/707ysegpumy/707/707_hd_achieving_allday_battery_life.mp4?dl=1
88 | http://devstreaming.apple.com/videos/wwdc/2015/702lp563ezbr/702/702_hd_apple_pay_within_apps.mp4?dl=1
89 | http://devstreaming.apple.com/videos/wwdc/2015/718b7aw9tq/718/718_hd_building_responsive_and_efficient_apps_with_gcd.mp4?dl=1
90 | http://devstreaming.apple.com/videos/wwdc/2015/710jle9eakx/710/710_hd_cloudkit_js_and_web_services.mp4?dl=1
91 | http://devstreaming.apple.com/videos/wwdc/2015/7153rwmu2r/715/715_hd_cloudkit_tips_and_tricks.mp4?dl=1
92 | http://devstreaming.apple.com/videos/wwdc/2015/708xzs0dtwx/708/708_hd_debugging_energy_issues.mp4?dl=1
93 | http://devstreaming.apple.com/videos/wwdc/2015/709jcaer6su/709/709_hd_introducing_search_apis.mp4?dl=1
94 | http://devstreaming.apple.com/videos/wwdc/2015/713gc2tqvvb/713/713_hd_introducing_watch_connectivity.mp4?dl=1
95 | http://devstreaming.apple.com/videos/wwdc/2015/7125ovmdf36/712/712_hd_low_energy_high_performance_compression_and_accelerate.mp4?dl=1
96 | http://devstreaming.apple.com/videos/wwdc/2015/711y6zlz0ll/711/711_hd_networking_with_nsurlsession.mp4?dl=1
97 | http://devstreaming.apple.com/videos/wwdc/2015/703kp2dwbwkr/703/703_hd_privacy_and_your_app.mp4?dl=1
98 | http://devstreaming.apple.com/videos/wwdc/2015/706nu20qkag/706/706_hd_security_and_your_apps.mp4?dl=1
99 | http://devstreaming.apple.com/videos/wwdc/2015/701i2qis0reg/701/701_hd_wallet__the_home_for_apple_pay_and_more.mp4?dl=1
100 | http://devstreaming.apple.com/videos/wwdc/2015/704ci202euy/704/704_hd_whats_new_in_cloudkit.mp4?dl=1
101 | http://devstreaming.apple.com/videos/wwdc/2015/714tqy593v/714/714_hd_whats_new_in_core_location.mp4?dl=1
102 | http://devstreaming.apple.com/videos/wwdc/2015/705qrxhfxo0/705/705_hd_whats_new_in_core_motion.mp4?dl=1
103 | http://devstreaming.apple.com/videos/wwdc/2015/717yq4y85w/717/717_hd_whats_new_in_network_extension_and_vpn.mp4?dl=1
104 | http://devstreaming.apple.com/videos/wwdc/2015/720xwbi9nl/720/720_hd_whats_new_in_notifications.mp4?dl=1
105 | http://devstreaming.apple.com/videos/wwdc/2015/719ui2k57m/719/719_hd_your_app_and_next_generation_networks.mp4?dl=1
106 |
--------------------------------------------------------------------------------
/WWDCSubGetter/String+Extensions.swift:
--------------------------------------------------------------------------------
1 | //
2 | // String+Extensions.swift
3 |
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 9/9/1395 AP.
6 | // Copyright © 1395 AP Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import CoreGraphics
10 | import CoreText
11 | import Foundation
12 |
13 | typealias RegexPattern = String
14 |
15 | extension CGColor {
16 | public static var yellow: CGColor {
17 | return CGColor(red: 1, green: 1, blue: 0, alpha: 1)
18 | }
19 | }
20 |
21 | extension String {
22 | /*
23 | Here's a way to change it on the fly with Swift, add an extension function to String:
24 | Assuming you have the regular Localizable.strings set up with lang_id.lproj ( e.g. en.lproj, de.lproj etc. ) you can use this anywhere you need:
25 | var val = "MY_LOCALIZED_STRING".localized("de")
26 | */
27 | func localized(lang:String) ->String {
28 | let path = Bundle.main.path(forResource: lang, ofType: "lproj")
29 | let bundle = Bundle(path: path!)
30 | return NSLocalizedString(self, tableName: nil, bundle: bundle!, value: "", comment: "")
31 | }
32 |
33 | func replace(_ letters: String, with newLetters: String) -> String {
34 | return self.replacingOccurrences(of: letters, with: newLetters, options: .literal, range: nil)
35 | }
36 |
37 | //MARK: - Regular Expression methods
38 |
39 | @available(OSX 10.12, *)
40 | func highlight(matches pattern: RegexPattern) -> NSAttributedString? {
41 |
42 | do {
43 | let regex = try NSRegularExpression(pattern: pattern, options: [])
44 | let range = NSMakeRange(0, self.count)
45 | let matches = regex.matches(in: self, options: [], range: range)
46 | let attributedText = NSMutableAttributedString(string: self)
47 |
48 | for match in matches {
49 | #if TARGET_OS_IPHONE
50 | // iOS code
51 | attributedText.addAttribute(NSBackgroundColorAttributeName, value: CGColor.yellow, range: match.range)
52 | #else
53 | // OSX code
54 | // attributedText.ad
55 | attributedText.addAttribute(NSAttributedString.Key(rawValue: kCTBackgroundColorAttributeName as String), value: CGColor.yellow, range: match.range)
56 |
57 | #endif
58 |
59 | }
60 | return attributedText.copy() as? NSAttributedString
61 |
62 | } catch let error as NSError {
63 | print(error.localizedDescription)
64 | return nil
65 | }
66 | }
67 |
68 | func list( matches pattern: RegexPattern) -> [String]? {
69 |
70 | do {
71 | let regex = try NSRegularExpression(pattern: pattern, options: [])
72 | let range = NSMakeRange(0, self.count)
73 | let matches = regex.matches(in: self, options: [], range: range)
74 | return matches.map {
75 | let range = $0.range
76 | return (self as NSString).substring(with: range)
77 | }
78 |
79 | } catch let error as NSError {
80 | print(error.localizedDescription)
81 | return nil
82 | }
83 | }
84 |
85 | func captureGroups(with pattern: RegexPattern) -> [String]? {
86 |
87 | do {
88 | let regex = try NSRegularExpression(pattern: pattern, options: [])
89 | let range = NSMakeRange(0, self.count)
90 | let matches = regex.matches(in: self, options: [], range: range)
91 |
92 | var groupMatches = [String]()
93 | for match in matches {
94 | let rangeCount = match.numberOfRanges
95 |
96 | for group in 0.. Bool {
109 |
110 | do {
111 | let regex = try NSRegularExpression(pattern: pattern, options: [])
112 | let range = NSMakeRange(0, self.count)
113 | return regex.firstMatch(in: self, options: [], range: range) != nil
114 | } catch let error as NSError {
115 | print(error.localizedDescription)
116 | return false
117 | }
118 | }
119 |
120 | func replace(matches pattern: RegexPattern, with replacementString: String) -> String? {
121 |
122 | do {
123 | let regex = try NSRegularExpression(pattern: pattern, options: [])
124 | let range = NSMakeRange(0, self.count)
125 | return regex.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: replacementString)
126 | } catch let error as NSError {
127 | print(error.localizedDescription)
128 | return nil
129 | }
130 | }
131 |
132 |
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Subtitle.swift:
--------------------------------------------------------------------------------
1 | /*
2 | Subtitle.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/24/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This file contains `Webvtt` and `Subtitle` structs.
10 | */
11 |
12 | import Foundation
13 |
14 | // MARK: - Subtitle
15 |
16 | struct Subtitle: Comparable {
17 |
18 | var id: ID {
19 | wwdcYear.id
20 | }
21 |
22 | var m3u8URL: URL {
23 | wwdcYear.m3u8URL
24 | }
25 |
26 | var videoURL: String {
27 | wwdcYear.videoURL
28 | }
29 |
30 | var webvtts: [Webvtt] {
31 | wwdcYear.webvtts
32 | }
33 |
34 | var wwdcYear: WWDCYear
35 |
36 | init?(videoURL: String, wwdc: WWDC) {
37 |
38 | let wwdcYear: WWDCYear? = {
39 |
40 | switch wwdc {
41 | case .of2024, .of2023, .of2022, .of2021:
42 | return WWDC2021(videoURL: videoURL)
43 | case .of2020:
44 | return WWDC2020(videoURL: videoURL)
45 | default:
46 | return WWDC2019(videoURL: videoURL)
47 | }
48 | }()
49 |
50 | guard let wwdcYear = wwdcYear else {
51 | return nil
52 | }
53 |
54 | self.wwdcYear = wwdcYear
55 | }
56 |
57 | mutating func updateWebvtts(with url: URL) throws {
58 | try wwdcYear.updateWebvtts(with: url)
59 | }
60 |
61 | func url(for webvtt: Webvtt) -> URL {
62 | wwdcYear.url(for: webvtt)
63 | }
64 |
65 | /**
66 | This method exports srt files with ordering webvtts in webvtts array
67 | and gatherin their contents in a single srt file.
68 | */
69 | func exportSrtFile() {
70 | var subString: String = ""
71 | let array = self.wwdcYear.webvtts.sorted()
72 | for Webvtt in array {
73 | subString += Webvtt.content
74 | }
75 |
76 | // Some of webvtts have some joint content. we need found this joint parts and delete them.
77 | var subArray = subString.components(separatedBy: "\n\n")
78 | subArray = subArray.removingDuplicates()
79 | subString = subArray.filter{!$0.isEmpty}.map{"\(subArray.firstIndex(of: $0)! + 1)\n" + $0 }.joined(separator: "\n\n\n") + "\n\n"
80 | self.saveSrtFileAtDestination(with: subString)
81 | SubtitlesProgress.changed()
82 | }
83 |
84 | /**
85 | This method saves given text as content of exported subtitle in a srt files.
86 |
87 | - note: We save two srt file in local url for both SD and HD versions of WWDC video.
88 |
89 | - parameter text: The content that should save in srt file.
90 | */
91 | private func saveSrtFileAtDestination(with text: String) {
92 |
93 | if let dir = model.destinationURL {
94 |
95 | //writing
96 | do {
97 | let folderPathHD = "/WWDC_\(self.wwdcYear.year)_Video_Subtitles/HD/"
98 | let folderPathSD = "/WWDC_\(self.wwdcYear.year)_Video_Subtitles/SD/"
99 | let hdPath = dir.path + folderPathHD
100 | let sdPath = dir.path + folderPathSD
101 | try FileManager.default.createDirectory(atPath: hdPath, withIntermediateDirectories: true, attributes: nil)
102 | try FileManager.default.createDirectory(atPath: sdPath, withIntermediateDirectories: true, attributes: nil)
103 |
104 | var path = dir.appendingPathComponent(folderPathHD + wwdcYear.subtitleNameForHD)
105 | try text.write(to: path, atomically: false, encoding: String.Encoding.utf8)
106 |
107 | path = dir.appendingPathComponent(folderPathSD + wwdcYear.subtitleNameForSD)
108 | try text.write(to: path, atomically: false, encoding: String.Encoding.utf8)
109 |
110 | }
111 | catch {/* error handling here */
112 | print(error.localizedDescription)
113 | }
114 | }
115 | }
116 |
117 | mutating func appendWebvtt(_ webvtt: Webvtt) {
118 | wwdcYear.webvtts.append(webvtt)
119 | }
120 |
121 | mutating func clearWebvtts() {
122 | wwdcYear.webvtts.removeAll()
123 | }
124 |
125 | }
126 |
127 | // We need to just compare `Subtitle`s according to their id, to sort in `Subtitle` webvtts array.
128 | func <(lhs: Subtitle, rhs: Subtitle) -> Bool {
129 | return lhs.wwdcYear.id < rhs.wwdcYear.id
130 | }
131 |
132 | func ==(lhs: Subtitle, rhs: Subtitle) -> Bool {
133 | return lhs.wwdcYear.id == rhs.wwdcYear.id
134 | }
135 |
136 |
137 | /// A struct for saving and managing video link informations.
138 | struct OldSubtitle: Comparable {
139 |
140 | /// This is the pattern we use to verify valid WWDC Video links.
141 | private let pattern = "(http(?:s)?:\\/\\/devstreaming[\\S\\w]*.apple.com\\/videos\\/[wdctuiorals]+\\/)(\\d+)(\\/\\w+\\/)(\\w+)(\\/)(\\w+(?:-)?\\w+)\\.m[op4][v4](?:\\?dl\\=1)?"
142 |
143 | private let pattern2020 = "(http(?:s)?:\\/\\/devstreaming[\\S\\w]*.apple.com\\/videos\\/[wdctuiorals]+\\/)(\\d+)(\\/)(\\d+)(\\/\\d+\\/)([a-zA-Z0-9\\-]+\\/)([a-zA-Z0-9\\-\\_]+)\\.m[op4][v4](?:\\?dl\\=1)?"
144 |
145 | private let pattern2021 = "(http(?:s)?:\\/\\/devstreaming[\\S\\w]*.apple.com\\/videos\\/[wdctuiorals]+\\/)(\\d+)(\\/)(\\d+)(\\/\\d+\\/)([a-zA-Z0-9\\-]+\\/)(downloads\\/)([a-zA-Z0-9\\-\\_]+)\\.m[op4][v4](?:\\?dl\\=1)?"
146 |
147 | /// A prefix of inputed video url which used for geting m3u8 and webvtt files url
148 | private let videoURLPrefix: String
149 |
150 | private let sessionNumber: Int
151 |
152 | var webvtts: [Webvtt] = []
153 |
154 | let wwdcYear: Int
155 |
156 | /// The given WWDC Video download url
157 | let videoURL: String
158 |
159 | /// The given WWDC Video name
160 | let videoName: String
161 |
162 | /*
163 | We export subtitles for both SD and HD version of videos.
164 | So we have subtitle name property for each of them.
165 | */
166 |
167 | /// Subtitle name for HD version of WWDC Video
168 | private var subtitleNameForHD: String {
169 | return self.videoName + ".srt"
170 | }
171 |
172 | /// Subtitle name for SD version of WWDC Video
173 | private var subtitleNameForSD: String {
174 | return self.subtitleNameForHD.replace("_hd_", with: "_sd_").replace("-HD", with: "-SD").replace("_hd.", with: "_sd.")
175 | }
176 |
177 | /// The url that used to download subtitle m3u8 file
178 | var m3u8URL: URL {
179 | if self.wwdcYear == 2020 {
180 | return URL(string: self.videoURLPrefix + "cc/en/en.m3u8")!
181 | }
182 | return URL(string:self.videoURLPrefix + "subtitles/eng/prog_index.m3u8")!
183 | }
184 |
185 | /// The id used to save subtitle in the model
186 | var id: ID {
187 | return Int("\(wwdcYear)\(sessionNumber)")!
188 | }
189 |
190 | /// We Check validation of inputed WWDC video url and if it wasn't valid we return nil in initialization.
191 | /// - parameter videoURL: WWDC video url which want to export its subtitle.
192 | init?(videoURL: String) {
193 | let regexGroup = videoURL.captureGroups(with: pattern)!
194 | let regexGroup2020 = videoURL.captureGroups(with: pattern2020)!
195 | let regexGroup2021 = videoURL.captureGroups(with: pattern2021)!
196 | if !regexGroup.isEmpty || !regexGroup2020.isEmpty || !regexGroup2021.isEmpty {
197 | /*
198 | If video url was valid we export some information from video url
199 | like video name , wwdc year, session number,... and save them to
200 | related properties.
201 | */
202 | var localSessionNumber = ""
203 | if !regexGroup.isEmpty {
204 | self.videoURL = regexGroup[0]
205 | self.videoURLPrefix = regexGroup[1...5].joined()
206 | self.videoName = regexGroup[6]
207 | self.wwdcYear = Int(regexGroup[2])!
208 | localSessionNumber = regexGroup[4]
209 |
210 | } else if !regexGroup2020.isEmpty {
211 | self.videoURL = regexGroup2020[0]
212 | self.videoURLPrefix = regexGroup2020[1...6].joined()
213 | self.videoName = regexGroup2020[7]
214 | self.wwdcYear = Int(regexGroup2020[2])!
215 | localSessionNumber = regexGroup2020[4]
216 |
217 | } else {
218 | self.videoURL = regexGroup2021[0]
219 | self.videoURLPrefix = regexGroup2021[1...6].joined()
220 | self.videoName = regexGroup2021[8]
221 | self.wwdcYear = Int(regexGroup2021[2])!
222 | localSessionNumber = regexGroup2021[4]
223 | }
224 |
225 | self.sessionNumber = Int(localSessionNumber) ?? {
226 |
227 | /*
228 | Some video links group 4 of pattern is'nt session number,
229 | so we should get the session number in other ways.
230 | If we failed to get session number in any way so we
231 | return a randomNumber as session number.
232 | - note: this number isn't use anywhere in exported srt file.
233 | */
234 | func randomNumber() -> Int {
235 | return Int(arc4random_uniform(100))*Int(arc4random_uniform(101))
236 | }
237 |
238 | if let group = regexGroup[3].captureGroups(with: "\\/(\\d+)\\w+\\/") {
239 | if let number = Int(group[1]) {
240 | return number
241 | }
242 | else {
243 | return randomNumber()
244 | }
245 | }
246 | else {
247 | return randomNumber()
248 | }
249 | }()
250 |
251 | }
252 | else {
253 | return nil
254 | }
255 | }
256 |
257 |
258 | /// This method gives url for download webvtt file related to given webvtt.
259 | func url(for webvtt: Webvtt) -> URL {
260 | if self.wwdcYear == 2020 {
261 | return URL(string: self.videoURLPrefix + "cc/en/" + webvtt.name)!
262 | }
263 | return URL(string: self.videoURLPrefix + "subtitles/eng/" + webvtt.name)!
264 | }
265 |
266 |
267 | }
268 |
269 |
270 | // We need to just compare `Subtitle`s according to their id, to sort in `Subtitle` webvtts array.
271 | func <(lhs: OldSubtitle, rhs: OldSubtitle) -> Bool {
272 | return lhs.id < rhs.id
273 | }
274 |
275 | func ==(lhs: OldSubtitle, rhs: OldSubtitle) -> Bool {
276 | return lhs.id == rhs.id
277 | }
278 |
--------------------------------------------------------------------------------
/WWDCSubGetter/SubtitlesProgress.swift:
--------------------------------------------------------------------------------
1 | /*
2 | SubtitlesProgress.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/24/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This file manages progress value of subtitle exporting for showing in `progressIndicator`.
10 | */
11 |
12 | import Cocoa
13 | /**
14 | We create a property of this protocol in `SubtitleProgress` and conform `MainViewController to this protocol to alert it about changes of subtitle exporting progress status.
15 | */
16 | protocol ProgressView {
17 | func progressChanged(to value: Double)
18 | }
19 |
20 | /**
21 | This struct is mediator between `Subtitle` struct and `MainViewController`
22 | to alert subtitles exporting status, and for this reason it's property and methods
23 | are static, because we need shared properties and methods for this purpose.
24 | every subtitle which wants export, calling `SubtitlesProgress.changed()` method of
25 | this struct, and this struct computes progress value of exporting entire subtitles
26 | which are in queue.
27 | */
28 |
29 | struct SubtitlesProgress {
30 |
31 | static var min: Double = 0
32 | static var max: Double = 100
33 | static var current: Double = 0
34 | static var unit: Double = 1.0
35 |
36 | static var progressView: ProgressView?
37 |
38 | // Here we compute new value of progress and calling `ProgressView` progressChanged method.
39 | static func changed() {
40 | self.current += self.unit
41 | self.progressView?.progressChanged(to: self.current)
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/WWDCSubGetter/TextFileView.swift:
--------------------------------------------------------------------------------
1 | /*
2 | TextFileView.swift
3 | WWDC.srt
4 |
5 | Created by Seyed Samad Gholamzadeh on 7/21/1396 AP.
6 | Copyright © 1396 AP Seyed Samad Gholamzadeh. All rights reserved.
7 |
8 | Abstract:
9 | This files contains method for enable drag and drop ability for TextFileView
10 | */
11 |
12 | import Cocoa
13 |
14 | /// protocol which called when a true text file dropped to view
15 | protocol TextFileViewDelegate {
16 |
17 | func droppedTextFileURL(_ url: URL)
18 | }
19 |
20 | /// A subclass of `NSView` which have ability to accept droped files to itself.
21 | final class TextFileView: NSView {
22 |
23 | enum Appearance {
24 | static let lineWidth: CGFloat = 10.0
25 | }
26 |
27 | var delegate: TextFileViewDelegate?
28 |
29 | override func awakeFromNib() {
30 | setup()
31 | }
32 |
33 | var acceptableTypes: Set {
34 |
35 | if #available(OSX 10.13, *) {
36 |
37 | return [.URL, .fileURL]
38 |
39 | } else {
40 |
41 | return [NSPasteboard.PasteboardType(rawValue: "public.file-url")]
42 | }
43 | }
44 |
45 | func setup() {
46 | registerForDraggedTypes(Array(acceptableTypes))
47 | }
48 |
49 | // We want just accept files that are text type
50 | lazy var filteringOptions = [NSPasteboard.ReadingOptionKey.urlReadingContentsConformToTypes:NSAttributedString.textTypes]
51 |
52 | func shouldAllowDrag(_ draggingInfo: NSDraggingInfo) -> Bool {
53 |
54 | var canAccept = false
55 |
56 | //2.
57 | let pasteBoard = draggingInfo.draggingPasteboard
58 |
59 | //3.
60 | if pasteBoard.canReadObject(forClasses: [NSURL.self], options: filteringOptions) {
61 | canAccept = true
62 | }
63 | return canAccept
64 |
65 | }
66 |
67 | override func draw(_ dirtyRect: NSRect) {
68 |
69 | if isReceivingDrag {
70 | NSColor.selectedControlColor.set()
71 |
72 | let path = NSBezierPath(rect:bounds)
73 | path.lineWidth = Appearance.lineWidth
74 | path.stroke()
75 | }
76 | }
77 |
78 | var isReceivingDrag = false {
79 | didSet {
80 | needsDisplay = true
81 | }
82 | }
83 |
84 | override func draggingEntered(_ sender: NSDraggingInfo) -> NSDragOperation {
85 | let allow = shouldAllowDrag(sender)
86 | isReceivingDrag = allow
87 | return allow ? .copy : NSDragOperation()
88 | }
89 |
90 |
91 | override func draggingExited(_ sender: NSDraggingInfo?) {
92 | isReceivingDrag = false
93 | }
94 |
95 | override func prepareForDragOperation(_ sender: NSDraggingInfo) -> Bool {
96 | let allow = shouldAllowDrag(sender)
97 | return allow
98 | }
99 |
100 | override func performDragOperation(_ draggingInfo: NSDraggingInfo) -> Bool {
101 |
102 | isReceivingDrag = false
103 | let pasteBoard = draggingInfo.draggingPasteboard
104 |
105 | if let urls = pasteBoard.readObjects(forClasses: [NSURL.self], options:filteringOptions) as? [URL], urls.count > 0 {
106 | delegate?.droppedTextFileURL(urls.first!)
107 | return true
108 | }
109 | return false
110 |
111 | }
112 | }
113 |
--------------------------------------------------------------------------------
/WWDCSubGetter/WWDC2019.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WWDC2019.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 9/14/23.
6 | // Copyright © 2023 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct WWDC2019: WWDCYear {
12 | let pattern: String = "(http(?:s)?:\\/\\/devstreaming[\\S\\w]*.apple.com\\/videos\\/[wdctuiorals]+\\/)(\\d+)(\\/\\w+\\/)(\\w+)(\\/)(\\w+(?:-)?\\w+)\\.m[op4][v4](?:\\?dl\\=1)?"
13 |
14 | let videoURLPrefix: String
15 |
16 | let sessionNumber: Int
17 |
18 | let year: String
19 |
20 | let videoURL: String
21 |
22 | let videoName: String
23 |
24 | var subtitleNameForHD: String {
25 | return self.videoName + ".srt"
26 | }
27 |
28 | var subtitleNameForSD: String {
29 | return self.subtitleNameForHD.replace("_hd_", with: "_sd_").replace("-HD", with: "-SD").replace("_hd.", with: "_sd.")
30 | }
31 |
32 | var m3u8URL: URL {
33 | return URL(string:self.videoURLPrefix + "subtitles/eng/prog_index.m3u8")!
34 | }
35 |
36 | var id: ID {
37 | return Int("\(year)\(sessionNumber)")!
38 | }
39 |
40 | func url(for webvtt: Webvtt) -> URL {
41 | return URL(string: self.videoURLPrefix + "subtitles/eng/" + webvtt.name)!
42 | }
43 |
44 | var webvtts: [Webvtt] = []
45 |
46 | init?(videoURL: String) {
47 | let regexGroup = videoURL.captureGroups(with: pattern)!
48 | if !regexGroup.isEmpty {
49 | /*
50 | If video url was valid we export some information from video url
51 | like video name , wwdc year, session number,... and save them to
52 | related properties.
53 | */
54 |
55 | self.videoURL = regexGroup[0]
56 | self.videoURLPrefix = regexGroup[1...5].joined()
57 | self.videoName = regexGroup[6]
58 | self.year = String(regexGroup[2])
59 | let localSessionNumber = regexGroup[4]
60 |
61 | self.sessionNumber = Int(localSessionNumber) ?? {
62 |
63 | /*
64 | Some video links group 4 of pattern is'nt session number,
65 | so we should get the session number in other ways.
66 | If we failed to get session number in any way so we
67 | return a randomNumber as session number.
68 | - note: this number isn't use anywhere in exported srt file.
69 | */
70 | func randomNumber() -> Int {
71 | return Int(arc4random_uniform(100))*Int(arc4random_uniform(101))
72 | }
73 |
74 | if let group = regexGroup[3].captureGroups(with: "\\/(\\d+)\\w+\\/") {
75 | if let number = Int(group[1]) {
76 | return number
77 | }
78 | else {
79 | return randomNumber()
80 | }
81 | }
82 | else {
83 | return randomNumber()
84 | }
85 | }()
86 |
87 | }
88 | else {
89 | return nil
90 | }
91 | }
92 |
93 |
94 | /*
95 | var subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("fileSequence")}
96 | if self.subtitle.wwdcYear >= 2021 {
97 | subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("sequence")}
98 | }
99 | */
100 | mutating func updateWebvtts(with url: URL) throws {
101 | /*
102 | We convert m3u8 file to an arry of `Webvtt` objects,
103 | which are the original subtitle files.
104 | We download and convert this webvtt files in a single srt file later.
105 | */
106 | let string = try String(contentsOf: url, encoding: String.Encoding.utf8)
107 | let subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("fileSequence")}
108 |
109 |
110 | let webvttArray = subsURLArray.map {Webvtt(number: subsURLArray.firstIndex(of: $0)!, content: "", name: $0)}
111 | self.webvtts = webvttArray
112 | }
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/WWDCSubGetter/WWDC2020.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WWDC2020.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 9/14/23.
6 | // Copyright © 2023 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct WWDC2020: WWDCYear {
12 | let pattern: String = "(http(?:s)?:\\/\\/devstreaming[\\S\\w]*.apple.com\\/videos\\/[wdctuiorals]+\\/)(\\d+)(\\/)(\\d+)(\\/\\d+\\/)([a-zA-Z0-9\\-]+\\/)([a-zA-Z0-9\\-\\_]+)\\.m[op4][v4](?:\\?dl\\=1)?"
13 |
14 | let videoURLPrefix: String
15 |
16 | let sessionNumber: Int
17 |
18 | let year: String
19 |
20 | let videoURL: String
21 |
22 | let videoName: String
23 |
24 | var subtitleNameForHD: String {
25 | return self.videoName + ".srt"
26 | }
27 |
28 | var subtitleNameForSD: String {
29 | return self.subtitleNameForHD.replace("_hd.", with: "_sd.")
30 | }
31 |
32 | var m3u8URL: URL {
33 | return URL(string: self.videoURLPrefix + "cc/en/en.m3u8")!
34 | }
35 |
36 | var id: ID {
37 | return Int("\(year)\(sessionNumber)")!
38 | }
39 |
40 | func url(for webvtt: Webvtt) -> URL {
41 | return URL(string: self.videoURLPrefix + "cc/en/" + webvtt.name)!
42 | }
43 |
44 | var webvtts: [Webvtt] = []
45 |
46 | init?(videoURL: String) {
47 | let regexGroup = videoURL.captureGroups(with: pattern)!
48 | if !regexGroup.isEmpty {
49 | /*
50 | If video url was valid we export some information from video url
51 | like video name , wwdc year, session number,... and save them to
52 | related properties.
53 | */
54 |
55 | self.videoURL = regexGroup[0]
56 | self.videoURLPrefix = regexGroup[1...6].joined()
57 | self.videoName = regexGroup[7]
58 | self.year = String(regexGroup[2])
59 | let localSessionNumber = regexGroup[4]
60 |
61 | self.sessionNumber = Int(localSessionNumber) ?? {
62 |
63 | /*
64 | Some video links group 4 of pattern is'nt session number,
65 | so we should get the session number in other ways.
66 | If we failed to get session number in any way so we
67 | return a randomNumber as session number.
68 | - note: this number isn't use anywhere in exported srt file.
69 | */
70 | func randomNumber() -> Int {
71 | return Int(arc4random_uniform(100))*Int(arc4random_uniform(101))
72 | }
73 |
74 | if let group = regexGroup[3].captureGroups(with: "\\/(\\d+)\\w+\\/") {
75 | if let number = Int(group[1]) {
76 | return number
77 | }
78 | else {
79 | return randomNumber()
80 | }
81 | }
82 | else {
83 | return randomNumber()
84 | }
85 | }()
86 |
87 | }
88 | else {
89 | return nil
90 | }
91 | }
92 |
93 |
94 | /*
95 | var subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("fileSequence")}
96 | if self.subtitle.wwdcYear >= 2021 {
97 | subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("sequence")}
98 | }
99 | */
100 | mutating func updateWebvtts(with url: URL) throws {
101 | /*
102 | We convert m3u8 file to an arry of `Webvtt` objects,
103 | which are the original subtitle files.
104 | We download and convert this webvtt files in a single srt file later.
105 | */
106 | let string = try String(contentsOf: url, encoding: String.Encoding.utf8)
107 | let subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("fileSequence")}
108 |
109 |
110 | let webvttArray = subsURLArray.map {Webvtt(number: subsURLArray.firstIndex(of: $0)!, content: "", name: $0)}
111 | self.webvtts = webvttArray
112 | }
113 |
114 |
115 | }
116 |
--------------------------------------------------------------------------------
/WWDCSubGetter/WWDC2021.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WWDC2021.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 9/14/23.
6 | // Copyright © 2023 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | struct WWDC2021: WWDCYear {
12 | let pattern: String = "(http(?:s)?:\\/\\/devstreaming[\\S\\w]*.apple.com\\/videos\\/[wdctuiorals]+\\/)(\\d+)(\\/)(\\d+)(\\/\\d+\\/)([a-zA-Z0-9\\-]+\\/)(downloads\\/)([a-zA-Z0-9\\-\\_]+)\\.m[op4][v4](?:\\?dl\\=1)?"
13 |
14 | let videoURLPrefix: String
15 |
16 | let sessionNumber: Int
17 |
18 | let year: String
19 |
20 | let videoURL: String
21 |
22 | let videoName: String
23 |
24 | var subtitleNameForHD: String {
25 | return self.videoName + ".srt"
26 | }
27 |
28 | var subtitleNameForSD: String {
29 | return self.subtitleNameForHD.replace("_hd.", with: "_sd.")
30 | }
31 |
32 | var m3u8URL: URL {
33 | URL(string:self.videoURLPrefix + "subtitles/eng/prog_index.m3u8")!
34 | }
35 |
36 | var id: ID {
37 | return Int("\(year)\(sessionNumber)")!
38 | }
39 |
40 | func url(for webvtt: Webvtt) -> URL {
41 | return URL(string: self.videoURLPrefix + "subtitles/eng/" + webvtt.name)!
42 | }
43 |
44 | var webvtts: [Webvtt] = []
45 |
46 | init?(videoURL: String) {
47 | let regexGroup = videoURL.captureGroups(with: pattern)!
48 | if !regexGroup.isEmpty {
49 | /*
50 | If video url was valid we export some information from video url
51 | like video name , wwdc year, session number,... and save them to
52 | related properties.
53 | */
54 |
55 | self.videoURL = regexGroup[0]
56 | self.videoURLPrefix = regexGroup[1...6].joined()
57 | self.videoName = regexGroup[8]
58 | self.year = String(regexGroup[2])
59 | let localSessionNumber = regexGroup[4]
60 |
61 | self.sessionNumber = Int(localSessionNumber) ?? {
62 |
63 | /*
64 | Some video links group 4 of pattern is'nt session number,
65 | so we should get the session number in other ways.
66 | If we failed to get session number in any way so we
67 | return a randomNumber as session number.
68 | - note: this number isn't use anywhere in exported srt file.
69 | */
70 | func randomNumber() -> Int {
71 | return Int(arc4random_uniform(100))*Int(arc4random_uniform(101))
72 | }
73 |
74 | if let group = regexGroup[3].captureGroups(with: "\\/(\\d+)\\w+\\/") {
75 | if let number = Int(group[1]) {
76 | return number
77 | }
78 | else {
79 | return randomNumber()
80 | }
81 | }
82 | else {
83 | return randomNumber()
84 | }
85 | }()
86 |
87 | }
88 | else {
89 | return nil
90 | }
91 | }
92 |
93 |
94 | /*
95 | var subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("fileSequence")}
96 | if self.subtitle.wwdcYear >= 2021 {
97 | subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("sequence")}
98 | }
99 | */
100 | mutating func updateWebvtts(with url: URL) throws {
101 | /*
102 | We convert m3u8 file to an arry of `Webvtt` objects,
103 | which are the original subtitle files.
104 | We download and convert this webvtt files in a single srt file later.
105 | */
106 | let string = try String(contentsOf: url, encoding: String.Encoding.utf8)
107 | // webvtt pattern before 2021
108 | let subsURLArray = string.components(separatedBy: "\n").filter {$0.contains("sequence")}
109 |
110 |
111 | let webvttArray = subsURLArray.map {Webvtt(number: subsURLArray.firstIndex(of: $0)!, content: "", name: $0)}
112 | self.webvtts = webvttArray
113 | }
114 |
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/WWDCSubGetter/WWDCVideosController.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WWDCVideosController.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 6/5/18.
6 | // Copyright © 2018 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 | import SystemConfiguration
11 |
12 |
13 | enum VideoQuality: String {
14 | case hd = "hd"
15 | case sd = "sd"
16 | }
17 |
18 |
19 | class WWDCVideosController {
20 |
21 | class func getSessionsList(fromHTML: String, wwdcYear: String) -> [String] {
22 |
23 | let nsFromHTML = fromHTML as NSString
24 | // let pat = "video-title\\\\\"\\>(.*?)\\<\\/h\\d\\>"
25 | let pat = "href=\"\\/videos\\/play\\/[^ ]*?\\/([0-9]*)\\/\".*?video-title\"\\>(.*?)\\<\\/h\\d\\>"
26 |
27 | let regex = try! NSRegularExpression(pattern: pat, options: NSRegularExpression.Options.dotMatchesLineSeparators)
28 | let matches = regex.matches(in: fromHTML, options: [], range: NSRange(location: 0, length: fromHTML.count))
29 |
30 | var sessionsListArray = [String]()
31 |
32 |
33 | matches.forEach { (match) in
34 | let numRange = match.range(at: 1)
35 | let titleRange = match.range(at: 2)
36 | let sessionNumber = nsFromHTML.substring(with: numRange) + " _ " + nsFromHTML.substring(with: titleRange)
37 |
38 | sessionsListArray.append(sessionNumber)
39 |
40 | }
41 |
42 | return sessionsListArray
43 | }
44 |
45 | class func getHDorSDdURLs(fromHTML: String, format: VideoQuality) -> (String) {
46 |
47 | let formatValue = format == .hd ? "[hH][dD]" : "[sS][dD]"
48 |
49 | // let pat = "\\b.*?(http(?:s)?://.*?" + formatValue + ".*?\\.m[op4][v4a])\\b"
50 | let pat = "(http(?:s)?[^ ]*?" + formatValue + "[^ ]*?)\\?dl"
51 | // let pat = "(http(?:s)?[^ ]*?" + formatValue + "[^ ]*?\\.(?:mp4|m4a|mov))"
52 | let regex = try! NSRegularExpression(pattern: pat, options: [])
53 | let matches = regex.matches(in: fromHTML, options: [], range: NSRange(location: 0, length: fromHTML.count))
54 |
55 | var videoURL = ""
56 | if !matches.isEmpty {
57 | let range = matches[0].range(at: 1)
58 | videoURL = (fromHTML as NSString).substring(with: range)
59 | }
60 |
61 | return videoURL
62 | }
63 |
64 | class func getPDFResourceURL(fromHTML: String) -> [String] {
65 | // let pat = "\\b.*(http(?:s)?://.*\\.pdf)\\b"
66 | let pat = "(http(?:s)?[^ ]*?\\.pdf)\\?dl"
67 |
68 | let regex = try! NSRegularExpression(pattern: pat, options: [])
69 | let matches = regex.matches(in: fromHTML, options: [], range: NSRange(location: 0, length: fromHTML.count))
70 | // var pdfResourceURL = ""
71 | // if !matches.isEmpty {
72 | // let range = matches[0].range(at: 1)
73 | // let r = fromHTML.index(fromHTML.startIndex, offsetBy: range.location) ..<
74 | // fromHTML.index(fromHTML.startIndex, offsetBy: range.location+range.length)
75 | // pdfResourceURL = String(fromHTML[r])
76 | // }
77 | //
78 | // return pdfResourceURL
79 |
80 | var pdfResourceURLPaths : [String] = []
81 | for match in matches {
82 | let range = match.range(at: 1)
83 | let path = (fromHTML as NSString).substring(with: range)
84 | pdfResourceURLPaths.append(path)
85 | }
86 |
87 | return pdfResourceURLPaths
88 | }
89 |
90 | class func getSampleCodeURL(fromHTML: String) -> [String] {
91 | let pat = "(href=\"[^ ]*?/content/samplecode/.*?=\")"
92 | let regex = try! NSRegularExpression(pattern: pat, options: [])
93 | let matches = regex.matches(in: fromHTML, options: [], range: NSRange(location: 0, length: fromHTML.count))
94 | var sampleURLPaths : [String] = []
95 | for match in matches {
96 | let range = match.range(at: 1)
97 | var path = (fromHTML as NSString).substring(with: range)
98 |
99 | // Tack on the hostname if it's not already there (some URLs are listed as
100 | // relative URL while some are fully-qualified).
101 | let prefixReplacementString: String
102 | if !path.contains("href=\"http") {
103 | prefixReplacementString = "http(?:s)?://developer.apple.com"
104 | } else {
105 | prefixReplacementString = ""
106 | }
107 | path = path.replacingOccurrences(of: "href=\"", with: prefixReplacementString)
108 |
109 | // Strip target attribute suffix
110 | path = path.replacingOccurrences(of: "\" target=\"", with: "/")
111 |
112 | sampleURLPaths.append(path)
113 | }
114 |
115 | var sampleArchivePaths : [String] = []
116 | for urlPath in sampleURLPaths {
117 | let jsonText = getStringContent(fromURL: urlPath + "book.json")
118 | if let data = jsonText.data(using: .utf8) {
119 | let object = try? JSONSerialization.jsonObject(with: data, options: .allowFragments)
120 | if let dictionary = object as? [String:Any] {
121 | if let relativePath = dictionary["sampleCode"] as? String {
122 | sampleArchivePaths.append(urlPath + relativePath)
123 | }
124 | }
125 | }
126 | }
127 |
128 | return sampleArchivePaths
129 | }
130 |
131 | class func getSampleCodeURL2(fromHTML: String) -> [String] {
132 | // let pat = "\\b.*(http(?:s)?://.*\\.zip)\\b"
133 | let pat = "(http(?:s)?[^ ]*?\\.zip)"
134 | let regex = try! NSRegularExpression(pattern: pat, options: [])
135 | let matches = regex.matches(in: fromHTML, options: [], range: NSRange(location: 0, length: fromHTML.count))
136 |
137 | var sampleURLPaths : [String] = []
138 | for match in matches {
139 | let range = match.range(at: 1)
140 | let path = (fromHTML as NSString).substring(with: range)
141 | sampleURLPaths.append(path)
142 | }
143 |
144 | return sampleURLPaths
145 | }
146 |
147 | class func getSampleCodeURL3(fromHTML: String) -> [String] {
148 | // let pat = "(href=\"[^ ]*?/content/samplecode/.*?=\")"
149 | let pat = "href=\"([^ ]*?/documentation/.*?)\""
150 | let regex = try! NSRegularExpression(pattern: pat, options: [])
151 | let matches = regex.matches(in: fromHTML, options: [], range: NSRange(location: 0, length: fromHTML.count))
152 | var sampleURLPaths : [String] = []
153 | for match in matches {
154 | let range = match.range(at: 1)
155 | var path = (fromHTML as NSString).substring(with: range)
156 |
157 | // Tack on the hostname if it's not already there (some URLs are listed as
158 | // relative URL while some are fully-qualified).
159 | let prefixReplacementString: String
160 | if !path.contains("href=\"http") {
161 | prefixReplacementString = "http(?:s)?://developer.apple.com"
162 | } else {
163 | prefixReplacementString = ""
164 | }
165 | path = path.replacingOccurrences(of: "href=\"", with: prefixReplacementString)
166 |
167 | // Strip target attribute suffix
168 | path = path.replacingOccurrences(of: "\" target=\"", with: "/")
169 |
170 | sampleURLPaths.append(path)
171 | }
172 |
173 | var sampleArchivePaths : [String] = []
174 | for urlPath in sampleURLPaths {
175 | let htmlText = getStringContent(fromURL: urlPath)
176 |
177 | let pat = "href=\"([^ ]*?/published/.*?.zip)\""
178 | let regex = try! NSRegularExpression(pattern: pat, options: [])
179 | let matches = regex.matches(in: htmlText, options: [], range: NSRange(location: 0, length: htmlText.count))
180 | for match in matches {
181 | let range = match.range(at: 1)
182 | let path = (htmlText as NSString).substring(with: range)
183 |
184 | sampleArchivePaths.append(path)
185 | }
186 |
187 | }
188 |
189 | return sampleArchivePaths
190 | }
191 |
192 |
193 | class func getStringContent(fromURL: String) -> (String) {
194 | /* Configure session, choose between:
195 | * defaultSessionConfiguration
196 | * ephemeralSessionConfiguration
197 | * backgroundSessionConfigurationWithIdentifier:
198 | And set session-wide properties, such as: HTTPAdditionalHeaders,
199 | HTTPCookieAcceptPolicy, requestCachePolicy or timeoutIntervalForRequest.
200 | */
201 |
202 | /* Create session, and optionally set a URLSessionDelegate. */
203 | let session = URLSession(configuration: URLSessionConfiguration.default, delegate: nil, delegateQueue: nil)
204 |
205 | /* Create the Request:
206 | My API (2) (GET https://developer.apple.com/videos/play/wwdc2017/201/)
207 | https://developer.apple.com/videos/play/wwdc2017/102/
208 | */
209 | var result = ""
210 | guard let URL = URL(string: fromURL) else {return result}
211 | var request = URLRequest(url: URL)
212 | request.httpMethod = "GET"
213 |
214 | /* Start a new Task */
215 | let semaphore = DispatchSemaphore.init(value: 0)
216 | let task = session.dataTask(with: request, completionHandler: { (data: Data?, response: URLResponse?, error: Error?) -> Void in
217 | if (error == nil) {
218 | /* Success */
219 | // let statusCode = (response as! NSHTTPURLResponse).statusCode
220 | // print("URL Session Task Succeeded: HTTP \(statusCode)")
221 | if let data = data, let string = String.init(data: data, encoding:
222 | .utf8) {
223 | result = string
224 |
225 | }
226 | }
227 | else {
228 | /* Failure */
229 | print("URL Session Task Failed: %@", error!.localizedDescription);
230 | }
231 |
232 | semaphore.signal()
233 | })
234 | task.resume()
235 | semaphore.wait()
236 | return result
237 | }
238 |
239 | class func downloadFile(urlString: String, forSession sessionIdentifier: String = "???") {
240 | var fileName = URL(fileURLWithPath: urlString).lastPathComponent
241 |
242 | if fileName.hasPrefix(sessionIdentifier) == false {
243 | fileName = "\(sessionIdentifier)_\(fileName)"
244 | }
245 |
246 | guard !FileManager.default.fileExists(atPath: "./" + fileName) else {
247 | print("\(fileName): already exists, nothing to do!")
248 | return
249 | }
250 |
251 | print("[Session \(sessionIdentifier)] Getting \(fileName) (\(urlString)):")
252 |
253 | guard URL(string: urlString) != nil else {
254 | print("<\(urlString)> is not valid URL!")
255 | return
256 | }
257 |
258 | // DownloadSessionManager.sharedInstance.downloadFile(fromURL: url, toPath: "\(fileName)")
259 | }
260 |
261 | }
262 |
263 |
264 | class Reachability {
265 | class func isConnectedToNetwork() -> Bool {
266 | guard let flags = getFlags() else { return false }
267 | let isReachable = flags.contains(.reachable)
268 | let needsConnection = flags.contains(.connectionRequired)
269 | return (isReachable && !needsConnection)
270 | }
271 |
272 | class func getFlags() -> SCNetworkReachabilityFlags? {
273 | guard let reachability = ipv4Reachability() ?? ipv6Reachability() else {
274 | return nil
275 | }
276 | var flags = SCNetworkReachabilityFlags()
277 | if !SCNetworkReachabilityGetFlags(reachability, &flags) {
278 | return nil
279 | }
280 | return flags
281 | }
282 |
283 | class func ipv6Reachability() -> SCNetworkReachability? {
284 | var zeroAddress = sockaddr_in6()
285 | zeroAddress.sin6_len = UInt8(MemoryLayout.size)
286 | zeroAddress.sin6_family = sa_family_t(AF_INET6)
287 |
288 | return withUnsafePointer(to: &zeroAddress, {
289 | $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
290 | SCNetworkReachabilityCreateWithAddress(nil, $0)
291 | }
292 | })
293 | }
294 |
295 | class func ipv4Reachability() -> SCNetworkReachability? {
296 | var zeroAddress = sockaddr_in()
297 | zeroAddress.sin_len = UInt8(MemoryLayout.size)
298 | zeroAddress.sin_family = sa_family_t(AF_INET)
299 |
300 | return withUnsafePointer(to: &zeroAddress, {
301 | $0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
302 | SCNetworkReachabilityCreateWithAddress(nil, $0)
303 | }
304 | })
305 | }
306 | }
307 |
--------------------------------------------------------------------------------
/WWDCSubGetter/WWDCYear.swift:
--------------------------------------------------------------------------------
1 | //
2 | // WWDCYear.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 9/14/23.
6 | // Copyright © 2023 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | protocol WWDCYear {
12 |
13 | var pattern: String { get }
14 |
15 | var webvtts: [Webvtt] { get set }
16 |
17 | var videoURLPrefix: String { get }
18 |
19 | var sessionNumber: Int { get }
20 |
21 | var year: String { get }
22 |
23 | var videoURL: String { get }
24 |
25 | var videoName: String { get }
26 |
27 | var subtitleNameForHD: String { get }
28 |
29 | var subtitleNameForSD: String { get }
30 |
31 | var m3u8URL: URL { get }
32 |
33 | var id: ID { get }
34 |
35 | func url(for webvtt: Webvtt) -> URL
36 |
37 | mutating func updateWebvtts(with url: URL) throws
38 | }
39 |
--------------------------------------------------------------------------------
/WWDCSubGetter/Webvtt.swift:
--------------------------------------------------------------------------------
1 | //
2 | // Webvtt.swift
3 | // WWDC.srt
4 | //
5 | // Created by Seyed Samad Gholamzadeh on 9/14/23.
6 | // Copyright © 2023 Seyed Samad Gholamzadeh. All rights reserved.
7 | //
8 |
9 | import Foundation
10 |
11 | // MARK: - Webvtt
12 |
13 | /**
14 | A struct for saving webvtt file informations, such as `name` and `number`
15 | of it in m3u8 file and the webvtt file content(after converting to srt stadard content).
16 | */
17 | struct Webvtt: Comparable {
18 |
19 | /// number of webvtt file in m3u8 file
20 | var number: Int
21 |
22 | /// content of webvtt file
23 | var content: String
24 |
25 | /// name of webvtt file in m3u8 file
26 | var name: String
27 | }
28 |
29 | // We need to just compare `Webvtt`s according to their number, to sort in `Subtitle` webvtts array.
30 | func <(lhs: Webvtt, rhs: Webvtt) -> Bool {
31 | return lhs.number < rhs.number
32 | }
33 |
34 | func ==(lhs: Webvtt, rhs: Webvtt) -> Bool {
35 | return lhs.number == rhs.number
36 | }
37 |
--------------------------------------------------------------------------------