Commit dfd2d52a by Wei Xian Ong

Initial commit

parent f14d2fde
//
// DailyPracticeVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/3.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class DailyPracticeVC: CustomNaviBar,UICollectionViewDelegate,UICollectionViewDelegateFlowLayout,UICollectionViewDataSource
{
@IBOutlet var dailyPracticeView: DailyPracticeView!
@IBOutlet weak var startBtn: UIButton!
@IBOutlet weak var nextBtn: UIButton!
@IBOutlet weak var previousBtn: UIButton!
@IBOutlet weak var finishView: UIView!
@IBOutlet weak var dailyCollectionView: UICollectionView!
let dailyTopic:[Int] = UserDefaults.standard.object(forKey: "dailyTopic") as? [Int] ??
[0,0,0,0,0,0]
var dailyTitleAry:[String] = []
var dailyImageAry:[String] = []
var currentPage:Int = 0{
didSet{
if dailyTitleAry.count == 1{
self.previousBtn.isHidden = true
self.nextBtn.isHidden = true
}else if currentPage == 0{
self.previousBtn.isHidden = true
}else if currentPage == self.dailyTitleAry.count - 1{
self.nextBtn.isHidden = true
}else{
self.previousBtn.isHidden = false
self.nextBtn.isHidden = false
}
// self.dailyPracticeView.showCurrentPageBorder(self.currentPage)
}
}
override func viewDidLoad() {
super.viewDidLoad()
self.dailyCollectionView.delegate = self
self.dailyCollectionView.dataSource = self
self.startBtn.layer.cornerRadius = 20
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
for i in 0 ..< dailyTopic.count{
if dailyTopic[i] != 0{
self.dailyTitleAry.append(VoiceDataManager.sharedInstance.dailyTitleAry[i])
self.dailyImageAry.append(VoiceDataManager.sharedInstance.dailyImageAry[i])
}
}
print(self.dailyTitleAry)
print(self.dailyImageAry)
if dailyTitleAry.count == 0{
AlertControllerModel.shareInstance.actionAlert(title: "error", massage: "尚無設定題目", btnTitle:"確認", controller: self) { (Void) in
self.navigationController?.popViewController(animated: true)
}
}
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return dailyTitleAry.count
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell:DailyCell = collectionView.dequeueReusableCell(withReuseIdentifier: "DailyPracticeCell", for: indexPath) as! DailyCell
cell.titleLabel.text = dailyTitleAry[indexPath.row]
cell.practiceImage.image = UIImage.init(named: dailyImageAry[indexPath.row])
return cell
}
// func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
// return CGSize.init(width: self.dailyCollectionView.frame.width, height: self.dailyCollectionView.frame.height)
// }
fileprivate var isScrolling = false
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
switch scrollView.tag {
case 0:
isScrolling = true
default:
break
}
}
func scrollViewDidScroll(_ scrollView: UIScrollView) {
switch scrollView.tag{
case 0:
if isScrolling == false{
let page = Int(round(scrollView.contentOffset.x/scrollView.bounds.width))
if currentPage != page{
currentPage = page
print(currentPage)
}
}
default:
break
}
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
switch scrollView.tag {
case 0:
isScrolling = false
default:
break
}
}
func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) {
switch scrollView.tag {
case 0:
if decelerate{
isScrolling = false
}
default:
break
}
}
@IBAction func startBtn(_ sender: Any) {
self.performSegue(withIdentifier: "gotoPracticeActionVC", sender: nil)
}
@IBAction func nextBtn(_ sender: Any) {
if currentPage < self.dailyTitleAry.count - 1{
self.dailyCollectionView.scrollToItem(at: IndexPath.init(row: self.currentPage + 1, section: 0), at: UICollectionView.ScrollPosition.centeredHorizontally, animated: true)
}
}
@IBAction func previosBtn(_ sender: Any) {
if self.currentPage > 0{
self.dailyCollectionView.scrollToItem(at: IndexPath.init(row: self.currentPage - 1, section: 0), at: UICollectionView.ScrollPosition.centeredHorizontally, animated: true)
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "gotoPracticeActionVC"{
let dailyPracticeActionVC: DailyPacticeActionVC = segue.destination as! DailyPacticeActionVC
dailyPracticeActionVC.practiceNum = Int(self.currentPage + 1)
dailyPracticeActionVC.userPracTopicAry = self.dailyTitleAry
if self.dailyTopic[self.currentPage] == 3{
dailyPracticeActionVC.timeCount = 300
}
else if self.dailyTopic[self.currentPage] == 2{
dailyPracticeActionVC.timeCount = 180
}
else if self.dailyTopic[self.currentPage] == 1{
dailyPracticeActionVC.timeCount = 60
}
else{
dailyPracticeActionVC.timeCount = 0
}
}
}
}
//
// DailyPracticeView.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/7.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class DailyPracticeView: UIView {
override func awakeFromNib() {
}
/*
// Only override draw() if you perform custom drawing.
// An empty implementation adversely affects performance during animation.
override func draw(_ rect: CGRect) {
// Drawing code
}
*/
}
//
// DashBoardVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/1.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
struct DataSet {
let title:String
let image:UIImage
let type:Item
}
enum Item:String{
//給自己設的型態添加選項
case gotoDailyPracticeVC
case gotoSetUpVC
case gotoHistoryVC
case gotoWeekPracticeVC
case goVoiceTeach
case gotoSpeechVC
}
class DashBoardVC: UIViewController,UITableViewDelegate,UITableViewDataSource {
@IBOutlet weak var DashBoardView: UITableView!
let dataSets:[DataSet] = [
DataSet.init(title: "每日練習", image: UIImage(named: "menu_1_button")!, type: .gotoDailyPracticeVC),
DataSet.init(title: "每週評量", image: UIImage(named: "menu_2_button")!, type: .gotoWeekPracticeVC),
DataSet.init(title: "嗓音衛教", image: UIImage(named: "water-glass")!, type: .goVoiceTeach),
DataSet.init(title: "歷史紀錄", image: UIImage(named: "menu_3_button")!, type: .gotoSpeechVC),
DataSet.init(title: "設 定", image: UIImage(named: "menu_5_button")!, type: .gotoSetUpVC)
]
override func viewDidLoad() {
super.viewDidLoad()
DashBoardView.delegate = self
DashBoardView.dataSource = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = true
}
override func viewWillDisappear(_ animated: Bool) {
self.navigationController?.isNavigationBarHidden = false
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return ((view.frame.height)/5)-2
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! DashBoardTableViewCell
cell.itemLabel.text = dataSets[indexPath.row].title
cell.itemImage.image = dataSets[indexPath.row].image
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: false)
performSegue(withIdentifier: dataSets[indexPath.row].type.rawValue, sender: nil)
}
}
//
// DoctorPassVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/30.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class DoctorPassVC: CustomNaviBar {
@IBOutlet weak var comfirmBtn: UIButton!
@IBOutlet weak var passwordText: UITextField!
override func viewDidLoad() {
super.viewDidLoad()
self.comfirmBtn.layer.cornerRadius = 20
// Do any additional setup after loading the view.
}
@IBAction func comfirmAction(_ sender: Any) {
if self.passwordText.text == "0"{
self.performSegue(withIdentifier: "gotoSettingDashBoard", sender: self)
}else{
AlertControllerModel.shareInstance.actionAlert(title: "error", massage: "密碼輸入錯誤", btnTitle: "確認", controller: self) { (Void) in
self.navigationController?.popViewController(animated: true)
}
}
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
//
// HistoryVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/16.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import RealmSwift
import Charts
class HistoryVC: CustomNaviBar, UIPickerViewDelegate, UIPickerViewDataSource{
@IBOutlet weak var dateBtn: UIButton!
@IBOutlet weak var pickDate: UIView!
@IBOutlet weak var pickView: UIPickerView!
@IBOutlet weak var graphView: BarChartView!
@IBOutlet weak var useVoiceBtn: UIButton!
@IBOutlet weak var switchBtn: UIButton!
@IBOutlet weak var personAssessBtn: UIButton!
var weekResult : Results<WeekPracticeData>?
var todayDate:String = ""
var dateArray:[String] = []
var yearMonth:[String] = []
var dayMonth:[String] = []
var selectDate:String = ""
var useVoiceAry:[Float] = []
enum switchType{
case useVoiceType
case personType
}
var selectType = switchType.useVoiceType
override func viewDidLoad() {
super.viewDidLoad()
self.todayDate = DateFormaterModel.shareInstance.timeStampToString(DateFormaterModel.shareInstance.getTimeStampSinceNow(num: 0), formatter: "yyyy-MM")
pickView.delegate = self
// Do any additional setup after loading the view.
//加入手勢
let tap = UITapGestureRecognizer(target: self, action: #selector(self.hidePickView(_:)))
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
}
override func viewWillAppear(_ animated: Bool) {
//確認有沒有內容
if RealmDataManager.shareInstance.getAllData().count > 0{
self.weekResult = RealmDataManager.shareInstance.getAllData()
self.saveDateToArray()
self.getUseVoiceDataFromRealm()
}else{
graphView.noDataText = "No Data"
AlertControllerModel.shareInstance.actionAlert(title: "Alert", massage: "尚無歷史紀錄", btnTitle: "確認", controller: self) { (Void) in
self.navigationController?.popViewController(animated: true)
}
}
}
//把realm裡的日期儲存到Ary
func saveDateToArray(){
var tmpMonthAry:[String] = [] //暫時儲存日期
var tmpYearMonth:[String] = [] //暫時儲存日期
var tmpDayMonth:[String] = []
for i in 0 ..< (self.weekResult?.count ?? 0){
tmpMonthAry.append(self.weekResult?[i].month ?? "")
}
self.dateArray.append(tmpMonthAry[0])
for date in tmpMonthAry{
if date != self.dateArray.last{
self.dateArray.append(date)
}
}
for i in 0 ..< self.dateArray.count{
let x = self.dateArray[i]
//取年月
let date = String(x.prefix(7))
tmpYearMonth.append(date)
}
for date in tmpYearMonth{
if date != self.yearMonth.last{
self.yearMonth.append(date)
}
}
for i in 0 ..< self.dateArray.count{
let x = self.dateArray[i]
//取日月
let date = String(x.suffix(5))
tmpDayMonth.append(date)
}
for date in tmpDayMonth{
if date != self.dayMonth.last{
self.dayMonth.append(date)
}
}
self.dateBtn.setTitle(todayDate, for: UIControl.State())
}
//MARK: - DatePickerView
func numberOfComponents(in pickerView: UIPickerView) -> Int {
return 1
}
func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int {
return yearMonth.count
}
func pickerView(_ pickerView: UIPickerView, titleForRow row: Int, forComponent component: Int) -> String? {
if self.yearMonth != nil{
return yearMonth[row]
}else{
return ""
}
}
func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) {
self.selectDate = yearMonth[row]
self.todayDate = yearMonth[row]
}
@IBAction func dateBtnAction(_ sender: UIButton) {
//顯示選擇器
UIView.animate(withDuration: 0.3) {
self.pickDate.frame.origin.y = 450
}
}
@IBAction func pickCancel(_ sender: Any) {
//隱藏顯示器
UIView.animate(withDuration: 0.3) {
self.pickDate.frame.origin.y = 800
}
}
@IBAction func pickDone(_ sender: Any) {
//隱藏顯示器
UIView.animate(withDuration: 0.3) {
self.pickDate.frame.origin.y = 800
}
self.dateBtn.setTitle(self.selectDate, for: UIControl.State())
switch self.selectType {
case .useVoiceType:
self.getUseVoiceDataFromRealm()
case .personType:
self.getPersonAssessDataFromRealm()
}
}
@objc func hidePickView(_ sender:UITapGestureRecognizer){
//隱藏顯示器
UIView.animate(withDuration: 0.3) {
self.pickDate.frame.origin.y = 800
}
}
//
func getUseVoiceDataFromRealm(){
var tmpUseVoice:[Double] = []
var selectDate:[String] = []
for i in 0 ..< (self.weekResult?.count ?? 0){
let x = self.dateArray[i]
//取年月
let date = String(x.prefix(7))
if(date == self.todayDate){
tmpUseVoice.append((self.weekResult?[i].useVoiceTotal)!)
let x = self.dateArray[i]
//取日月
let date = String(x.suffix(5))
selectDate.append(date)
}
}
self.drawGraph(dataPoints: selectDate, values: tmpUseVoice, yAxis: 22)
}
func getPersonAssessDataFromRealm(){
var tmpPersonAssess:[Double] = []
var selectDate:[String] = []
for i in 0 ..< (self.weekResult?.count ?? 0){
let x = self.dateArray[i]
//取年月
let date = String(x.prefix(7))
if(date == self.todayDate){
tmpPersonAssess.append((self.weekResult?[i].personAssessResult)!)
let x = self.dateArray[i]
//取日月
let date = String(x.suffix(5))
selectDate.append(date)
}
}
self.drawGraph(dataPoints: selectDate, values: tmpPersonAssess, yAxis: 41)
}
//MARK: - Draw Graph
func drawGraph(dataPoints: [String], values: [Double], yAxis: Double){
var dataEntries: [BarChartDataEntry] = []
for i in 0 ..< dataPoints.count{
let dataEntry = BarChartDataEntry(x: Double(i), y: values[i])
dataEntries.append(dataEntry)
}
let chartDataSet = BarChartDataSet(entries: dataEntries)
let chartData = BarChartData(dataSet: chartDataSet)
graphView.data = chartData
graphView.leftAxis.enabled = false //left
graphView.rightAxis.enabled = false //right
graphView.drawGridBackgroundEnabled = false
graphView.legend.enabled = false //設定
graphView.xAxis.drawGridLinesEnabled = false //bar後面的線
chartData.barWidth = 0.1 //bar寬度
graphView.leftAxis.axisMinimum = (yAxis - yAxis) //最小高度
graphView.leftAxis.axisMaximum = yAxis //最大高度
graphView.isUserInteractionEnabled = false //使用者互動
chartDataSet.colors = [#colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1)] //bar顏色
graphView.animate(xAxisDuration: 2.0, yAxisDuration: 2.0, easingOption: .easeInCubic) //動畫
//設定x軸字
let xAxis = graphView.xAxis
xAxis.labelPosition = .bottom
xAxis.labelCount = dataPoints.count
xAxis.valueFormatter = IndexAxisValueFormatter(values: dataPoints)
}
@IBAction func switchToUseVoice(_ sender: Any) {
self.selectType = .useVoiceType
self.useVoiceBtn.setTitleColor(#colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1), for: UIControl.State())
self.personAssessBtn.setTitleColor(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), for: UIControl.State())
self.switchBtn.isSelected = false
self.getUseVoiceDataFromRealm()
}
@IBAction func switchType(_ sender: Any) {
if self.selectType == .useVoiceType{
self.selectType = .personType
self.useVoiceBtn.setTitleColor(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), for: UIControl.State())
self.personAssessBtn.setTitleColor(#colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1), for: UIControl.State())
self.switchBtn.isSelected = true
self.getPersonAssessDataFromRealm()
}else{
self.selectType = .useVoiceType
self.useVoiceBtn.setTitleColor(#colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1), for: UIControl.State())
self.personAssessBtn.setTitleColor(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), for: UIControl.State())
self.switchBtn.isSelected = false
self.getUseVoiceDataFromRealm()
}
}
@IBAction func switchToPerson(_ sender: Any) {
self.selectType = .personType
self.useVoiceBtn.setTitleColor(#colorLiteral(red: 0, green: 0, blue: 0, alpha: 1), for: UIControl.State())
self.personAssessBtn.setTitleColor(#colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1), for: UIControl.State())
self.switchBtn.isSelected = true
self.getPersonAssessDataFromRealm()
}
}
//
// ViewController.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/1.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import GoogleSignIn
import GoogleAPIClientForREST
import GTMSessionFetcher
class LoginVC: UIViewController{
@IBOutlet weak var startBtn: UIButton!
let googleDriveService = GTLRDriveService()
var googleUser: GIDGoogleUser?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
GIDSignIn.sharedInstance()?.delegate = self
GIDSignIn.sharedInstance()?.uiDelegate = self
GIDSignIn.sharedInstance()?.scopes = [kGTLRAuthScopeDrive]
GIDSignIn.sharedInstance()?.signInSilently()
self.startBtn.layer.cornerRadius = 20
}
override func viewWillAppear(_ animated: Bool) {
GIDSignIn.sharedInstance()?.signIn()
}
@IBAction func fristLunch(_ sender: Any) {
self.performSegue(withIdentifier: "gotoDashBoardVC", sender: nil)
// GoogleDriveManager.sharedInstanse.createFloder(name: "test123", service: googleDriveService) { (String) in
// print(String)
// }
}
}
extension LoginVC: GIDSignInDelegate,GIDSignInUIDelegate{
public func sign(_ signIn: GIDSignIn!, didSignInFor user: GIDGoogleUser!, withError error: Error!) {
// A nil error indicates a successful login
if error == nil{
self.googleDriveService.authorizer = user.authentication.fetcherAuthorizer()
// self.googleUser = user
// GoogleDriveManager.sharedInstanse.getFolderID(service: googleDriveService) { (Void) in
// print(Void)
// }
}else{
self.googleDriveService.authorizer = nil
self.googleUser = nil
}
}
}
//
// SpeechSetVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/31.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class SpeechSetVC: CustomNaviBar, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout {
@IBOutlet weak var speechSetCollectionView: UICollectionView!
let index = ["不當行為","說話量","清喉嚨","喝茶/咖啡","抽煙","喝水","練習","SAVE"]
var selectIndex = [0,0,0,0,0,0,0,0]
override func viewDidLoad() {
super.viewDidLoad()
self.speechSetCollectionView.delegate = self
self.speechSetCollectionView.dataSource = self
// Do any additional setup after loading the view.
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 8
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = speechSetCollectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) as! PersonSetCell
cell.label.text = index[indexPath.row]
if selectIndex[indexPath.row] == 1{
cell.layer.borderWidth = 10.0
cell.layer.borderColor = #colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1)
}else{
cell.layer.borderWidth = 0
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: ((self.speechSetCollectionView.frame.width/2) - 5), height: (self.speechSetCollectionView.frame.height/4) - 7)
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
if indexPath.row == 7{
AlertControllerModel.shareInstance.actionAlert(title: "Succes", massage: "儲存成功", btnTitle: "確認", controller: self) { (Void) in
self.navigationController?.popViewController(animated: true)
UserDefaults.standard.set(self.selectIndex, forKey: "weeklyRecord")
}
}else{
if self.selectIndex[indexPath.row] == 0{
self.selectIndex[indexPath.row] = 1
}else{
self.selectIndex[indexPath.row] = 0
}
print(indexPath)
self.speechSetCollectionView.reloadData()
}
}
@IBAction func backNaviBtn(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
}
//
// SpeedTestSwitchVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/8/3.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class SpeedTestSwitchVC: CustomNaviBar {
@IBOutlet weak var onBtn: UIButton!
@IBOutlet weak var offBtn: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// if let speechOn : Bool = UserDefaults.standard.bool(forKey: "speechOn"){
// self.setSelectBtn(speechOn)
// }
onBtn.layer.borderColor = #colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1)
offBtn.layer.borderColor = #colorLiteral(red: 0.7428085208, green: 0.4578476548, blue: 0.5003266931, alpha: 1)
// Do any additional setup after loading the view.
}
@IBAction func onOffBtnAction(_ sender: UIButton) {
switch sender.tag {
case 0:
self.selectBtn(sender)
UserDefaults.standard.set(true, forKey: "speechOn")
case 1:
self.selectBtn(sender)
UserDefaults.standard.set(false, forKey: "speechOn")
default:
break
}
}
@IBAction func backNaviBar(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
func setSelectBtn(_ isOn:Bool){
if isOn{
self.selectBtn(onBtn)
}else{
self.selectBtn(offBtn)
}
}
func selectBtn(_ button : UIButton){
onBtn.layer.borderWidth = 0.0
offBtn.layer.borderWidth = 0.0
button.layer.borderWidth = 8.0
}
}
//
// VoiceTeachVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/15.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class VoiceTeachVC: CustomNaviBar, UIScrollViewDelegate {
@IBOutlet weak var scrollView: UIScrollView!
@IBOutlet weak var pageControl: UIPageControl!
var screenSize: CGSize!
var image: UIImageView!
let imageName = ["teach_1","teach_2","teach_3","teach_4","teach_5","teach_6","teach_7"]
override func viewDidLoad() {
super.viewDidLoad()
screenSize = UIScreen.main.bounds.size
scrollView.contentSize = CGSize(width:screenSize.width * 7, height: screenSize.height - 30)
self.scrollView.delegate = self
for i in 0...6{
image = UIImageView()
image.frame = CGRect(x: screenSize.width * CGFloat(i), y: 0, width: screenSize.width, height: screenSize.height - 30)
image.image = UIImage(named: imageName[i])
self.scrollView.addSubview(image)
}
// Do any additional setup after loading the view.
}
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
pageControl.currentPage = Int(scrollView.contentOffset.x/scrollView.frame.size.width)
}
@IBAction func switchPage(_ sender: UIPageControl) {
var frame = scrollView.frame
frame.origin.x = frame.size.width * CGFloat(sender.currentPage)
frame.origin.y = 0
scrollView.scrollRectToVisible(frame, animated: true)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
//
// setDailyNotiVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/27.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class setDailyNotiVC: UIViewController {
@IBOutlet weak var firstTimeBtn: UIButton!
@IBOutlet weak var secondTimeBtn: UIButton!
@IBOutlet weak var thridTimeBtn: UIButton!
@IBOutlet weak var pickTime: UIView!
@IBOutlet weak var timePicker: UIDatePicker!
@IBOutlet weak var firstSwitch: UISwitch!
@IBOutlet weak var secondSwitch: UISwitch!
@IBOutlet weak var thridSwitch: UISwitch!
@IBOutlet weak var timePickerDoneBtn: UIBarButtonItem!
var pickedTime:String = ""
var chosenBtn:Int = 0
var date:Date = Date()
var dateAry:[Date] = UserDefaults.standard.object(forKey: "dateAry") as! [Date]
override func viewDidLoad() {
super.viewDidLoad()
let tap = UITapGestureRecognizer(target: self, action: #selector(self.hidePickTime(_:)))
view.addGestureRecognizer(tap)
view.isUserInteractionEnabled = true
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.firstTimeBtn.setTitle((UserDefaults.standard.object(forKey: "firstBtn") as? String ?? "00:00"), for: UIControl.State())
self.secondTimeBtn.setTitle((UserDefaults.standard.object(forKey: "secondBtn") as? String ?? "00:00"), for: UIControl.State())
self.thridTimeBtn.setTitle((UserDefaults.standard.object(forKey: "thridBtn") as? String ?? "00:00"), for: UIControl.State())
self.firstSwitch.setOn((UserDefaults.standard.object(forKey: "firstSwitch") as? Bool ?? false), animated: false)
self.secondSwitch.setOn((UserDefaults.standard.object(forKey: "secondSwitch") as? Bool ?? false), animated: false)
self.thridSwitch.setOn((UserDefaults.standard.object(forKey: "thridSwitch") as? Bool ?? false), animated: false)
}
@IBAction func closeBtn(_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
@IBAction func firstBtn(_ sender: Any) {
UIView.animate(withDuration: 0.3) {
self.pickTime.frame.origin.y = 456
}
self.chosenBtn = 1
self.disableAllBtn()
}
@IBAction func secondBtn(_ sender: Any) {
UIView.animate(withDuration: 0.3) {
self.pickTime.frame.origin.y = 456
}
self.chosenBtn = 2
self.disableAllBtn()
}
@IBAction func thridBtn(_ sender: Any) {
UIView.animate(withDuration: 0.3) {
self.pickTime.frame.origin.y = 456
}
self.chosenBtn = 3
self.disableAllBtn()
}
@IBAction func pickTimeCancel(_ sender: Any) {
UIView.animate(withDuration: 0.3) {
self.pickTime.frame.origin.y = 800
}
self.enableAllBtn()
}
@IBAction func donePickTime(_ sender: Any) {
UIView.animate(withDuration: 0.3) {
self.pickTime.frame.origin.y = 800
}
self.enableAllBtn()
switch chosenBtn {
case 1:
self.firstTimeBtn.setTitle(pickedTime, for: UIControl.State())
UserDefaults.standard.set(pickedTime, forKey: "firstBtn")
dateAry[0] = date
if self.firstSwitch.isOn{
LocalNotificationManager.shareInstanse.createLocalNotification(setdate: dateAry[0], identifier: "first")
}
case 2:
self.secondTimeBtn.setTitle(pickedTime, for: UIControl.State())
UserDefaults.standard.set(pickedTime, forKey: "secondBtn")
dateAry[1] = date
if self.secondSwitch.isOn{
LocalNotificationManager.shareInstanse.createLocalNotification(setdate: dateAry[1], identifier: "second")
}
case 3:
self.thridTimeBtn.setTitle(pickedTime, for: UIControl.State())
UserDefaults.standard.set(pickedTime, forKey: "thridBtn")
dateAry[2] = date
if self.thridSwitch.isOn{
LocalNotificationManager.shareInstanse.createLocalNotification(setdate: dateAry[2], identifier: "thrid")
}
default:
return
}
}
@IBAction func pickingTime(_ sender: UIDatePicker) {
let dateFormatter = DateFormatter()
dateFormatter.timeStyle = DateFormatter.Style.short
self.pickedTime = dateFormatter.string(from: timePicker.date)
self.timePickerDoneBtn.isEnabled = true
self.date = timePicker.date
}
@objc func hidePickTime(_ : UITapGestureRecognizer){
UIView.animate(withDuration: 0.3) {
self.pickTime.frame.origin.y = 800
}
self.enableAllBtn()
}
func disableAllBtn(){
self.firstTimeBtn.isEnabled = false
self.secondTimeBtn.isEnabled = false
self.thridTimeBtn.isEnabled = false
self.firstSwitch.isEnabled = false
self.secondSwitch.isEnabled = false
self.thridSwitch.isEnabled = false
self.timePickerDoneBtn.isEnabled = false
}
func enableAllBtn(){
self.firstTimeBtn.isEnabled = true
self.secondTimeBtn.isEnabled = true
self.thridTimeBtn.isEnabled = true
self.firstSwitch.isEnabled = true
self.secondSwitch.isEnabled = true
self.thridSwitch.isEnabled = true
}
@IBAction func firstSwitch(_ sender: UISwitch) {
if sender.isOn{
UserDefaults.standard.set(true, forKey: "firstSwitch")
LocalNotificationManager.shareInstanse.createLocalNotification(setdate: dateAry[0], identifier: "first")
}else{
LocalNotificationManager.shareInstanse.cancelNotification(identifier: "first")
UserDefaults.standard.set(false, forKey: "firstSwitch")
}
}
@IBAction func secondSwitch(_ sender: UISwitch) {
if sender.isOn{
LocalNotificationManager.shareInstanse.createLocalNotification(setdate: dateAry[1], identifier: "second")
UserDefaults.standard.set(true, forKey: "secondSwitch")
}else{
LocalNotificationManager.shareInstanse.cancelNotification(identifier: "second")
UserDefaults.standard.set(false, forKey: "secondSwitch")
}
}
@IBAction func thridSwitch(_ sender: UISwitch) {
if sender.isOn{
LocalNotificationManager.shareInstanse.createLocalNotification(setdate: dateAry[2], identifier: "thrid")
UserDefaults.standard.set(true, forKey: "thridSwitch")
}else{
LocalNotificationManager.shareInstanse.cancelNotification(identifier: "thrid")
UserDefaults.standard.set(false, forKey: "thridSwitch")
}
}
}
//
// setDailyPracticeVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/30.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class setDailyPracticeVC: CustomNaviBar, UICollectionViewDelegate, UICollectionViewDataSource, UICollectionViewDelegateFlowLayout{
@IBOutlet weak var relexCollectionView: UICollectionView!
@IBOutlet weak var zeroLabel: UILabel!
@IBOutlet weak var relaxLabel: UILabel!
var selectIndex:[Int] = (UserDefaults.standard.object(forKey: "dailyTopic") as? [Int] ?? [0,0,0,0,0,0])
override func viewDidLoad() {
super.viewDidLoad()
self.relexCollectionView.delegate = self
self.relexCollectionView.dataSource = self
// Do any additional setup after loading the view.
}
func numberOfSections(in collectionView: UICollectionView) -> Int {
return 6
}
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
return 4
}
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
let cell = relexCollectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath)
if indexPath.row == self.selectIndex[indexPath.section]{
cell.backgroundColor = #colorLiteral(red: 0.8556168675, green: 0.6481509805, blue: 0.6086260676, alpha: 1)
}else{
cell.backgroundColor = #colorLiteral(red: 0.8470258117, green: 0.8474709988, blue: 0.8265308738, alpha: 1)
}
return cell
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, sizeForItemAt indexPath: IndexPath) -> CGSize {
return CGSize(width: self.zeroLabel.bounds.size.width, height: self.relaxLabel.bounds.size.height)
}
func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, minimumInteritemSpacingForSectionAt section: Int) -> CGFloat {
return 0
}
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
self.selectIndex[indexPath.section] = indexPath.row
self.relexCollectionView.reloadData()
print(self.selectIndex)
}
@IBAction func saveBtn(_ sender: Any) {
AlertControllerModel.shareInstance.actionAlert(title: "Success", massage: "儲存成功", btnTitle: "確認", controller: self) { (Void) in
UserDefaults.standard.set(self.selectIndex, forKey: "dailyTopic")
self.navigationController?.popViewController(animated: true)
}
}
@IBAction func backNaviBtn(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
}
//
// ViewController.swift
// speedVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/1.
// Created by Wei Xian Ong on 2020/8/5.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class ViewController: UIViewController {
class speedVC: CustomNaviBar {
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
AlertControllerModel.shareInstance.actionAlert(title: "Alert", massage: "尚無歷史紀錄", btnTitle: "確認", controller: self) { (Void) in
self.navigationController?.popViewController(animated: true)
}
}
}
//
// weaklyRecordVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/14.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class weaklyRecordVC: CustomNaviBar, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var previousBtn: UIButton!
@IBOutlet weak var nextBtn: UIButton!
@IBOutlet weak var date: UILabel!
@IBOutlet weak var page: UILabel!
@IBOutlet weak var question: UILabel!
@IBOutlet weak var weaklyRecordTableView: UITableView!
@IBOutlet weak var finishView: UIView!
@IBOutlet weak var finishBtn: UIButton!
var weekUseVoiceDic:[[String]] = []
var setTopic:[Int] = UserDefaults.standard.object(forKey: "weeklyRecord") as? [Int] ?? [0,0,0,0,0,0,0]
var currentPage:Int = 0{
didSet{
self.page.text = VoiceDataManager.sharedInstance.weekUseVoiceDic[currentPage]![0]
self.question.text = VoiceDataManager.sharedInstance.weekUseVoiceDic[currentPage]![1]
if currentPage == 0{
self.previousBtn.isHidden = true
self.nextBtn.setTitle("下一題", for: UIControl.State())
}else if currentPage == VoiceDataManager.sharedInstance.weekUseVoiceDic.count - 1{
self.nextBtn.setTitle("完成", for: UIControl.State())
}else{
self.previousBtn.isHidden = false
self.nextBtn.setTitle("下一題", for: UIControl.State())
self.nextBtn.isHidden = false
}
}
}
var selectIndex:IndexPath? = [0,3]
override func viewDidLoad() {
super.viewDidLoad()
weaklyRecordTableView.delegate = self
weaklyRecordTableView.dataSource = self
// Do any additional setup after loading the view.
currentPage = 0
}
override func viewWillAppear(_ animated: Bool) {
for i in 0 ..< 6 {
if setTopic[i] == 1{
weekUseVoiceDic.append(VoiceDataManager.sharedInstance.weekUseVoiceDic[i]!)
}
}
print(weekUseVoiceDic)
if currentPage == 0 {
previousBtn.isHidden = true
}
else{
previousBtn.isHidden = false
}
self.question.text = self.weekUseVoiceDic[currentPage][0]
self.question.text = self.weekUseVoiceDic[currentPage][1]
self.date.text = DateFormaterModel.shareInstance.timeStampToString(DateFormaterModel.shareInstance.getTimeStampSinceNow(num: 0), formatter: "yyyy/MM/dd")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return weaklyRecordTableView.frame.height/4
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 4
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "weaklyRecord", for: indexPath) as!
weaklyRecordTableCell
cell.pickIcon.image = UIImage(named: "answer_button_n")
cell.answer.text = self.weekUseVoiceDic[self.currentPage][indexPath.row + 2]
if (VoiceDataManager.sharedInstance.weekUseVoice[currentPage] == indexPath.row){
cell.pickIcon.image = UIImage(named: "answer_button_h")
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
weaklyRecordTableView.deselectRow(at: indexPath, animated: false)
selectIndex = indexPath
VoiceDataManager.sharedInstance.weekUseVoice[currentPage] = selectIndex?.row as! Int
self.weaklyRecordTableView.reloadData()
}
@IBAction func nextQuestion(_ sender: Any) {
if currentPage < self.weekUseVoiceDic.count - 1{
self.currentPage = self.currentPage + 1
self.weaklyRecordTableView.reloadData()
}else{
performSegue(withIdentifier: "gotoWeekPersonAssessView", sender: nil)
}
}
@IBAction func previosQuestion(_ sender: Any) {
self.currentPage = self.currentPage - 1
self.weaklyRecordTableView.reloadData()
}
}
//
// weaklyRecordVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/14.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class weekPersonAssessVC: CustomNaviBar, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var previousBtn: UIButton!
@IBOutlet weak var nextBtn: UIButton!
@IBOutlet weak var date: UILabel!
@IBOutlet weak var page: UILabel!
@IBOutlet weak var question: UILabel!
@IBOutlet weak var weaklyRecordTableView: UITableView!
@IBOutlet weak var finishView: UIView!
@IBOutlet weak var finishBtn: UIButton!
var currentPage:Int = 0{
didSet{
self.page.text = VoiceDataManager.sharedInstance.weekPersonAssessDic[currentPage]![0]
self.question.text = VoiceDataManager.sharedInstance.weekPersonAssessDic[currentPage]![1]
if currentPage == 0{
self.previousBtn.isHidden = true
self.nextBtn.setTitle("下一題", for: UIControl.State())
}else if currentPage == VoiceDataManager.sharedInstance.weekPersonAssessDic.count - 1{
self.nextBtn.setTitle("完成", for: UIControl.State())
}else{
self.previousBtn.isHidden = false
self.nextBtn.setTitle("下一題", for: UIControl.State())
self.nextBtn.isHidden = false
}
}
}
var selectIndex:IndexPath? = [0,0]
override func viewDidLoad() {
super.viewDidLoad()
weaklyRecordTableView.delegate = self
weaklyRecordTableView.dataSource = self
// Do any additional setup after loading the view.
currentPage = 0
self.finishBtn.layer.cornerRadius = 20
}
override func viewWillAppear(_ animated: Bool) {
if currentPage == 0 {
previousBtn.isHidden = true
}
else{
previousBtn.isHidden = false
}
self.question.text = VoiceDataManager.sharedInstance.weekPersonAssessDic[currentPage]![0]
self.question.text = VoiceDataManager.sharedInstance.weekPersonAssessDic[currentPage]![1]
self.date.text = DateFormaterModel.shareInstance.timeStampToString(DateFormaterModel.shareInstance.getTimeStampSinceNow(num: 0), formatter: "yyyy/MM/dd")
}
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return weaklyRecordTableView.frame.height/5
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 5
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: "weaklyRecord", for: indexPath) as!
weaklyRecordTableCell
cell.pickIcon.image = UIImage(named: "answer_button_n")
cell.answer.text = VoiceDataManager.sharedInstance.weekPersonAssessDic[self.currentPage]?[indexPath.row + 2]
if (VoiceDataManager.sharedInstance.weekPersonAssess[currentPage] == indexPath.row){
cell.pickIcon.image = UIImage(named: "answer_button_h")
}
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
weaklyRecordTableView.deselectRow(at: indexPath, animated: false)
selectIndex = indexPath
VoiceDataManager.sharedInstance.weekPersonAssess[currentPage] = indexPath.row
print(VoiceDataManager.sharedInstance.weekPersonAssess)
self.weaklyRecordTableView.reloadData()
}
@IBAction func nextQuestion(_ sender: Any) {
if currentPage < VoiceDataManager.sharedInstance.weekPersonAssessDic.count - 1{
self.currentPage = self.currentPage + 1
self.weaklyRecordTableView.reloadData()
}else{
self.finishView.isHidden = false
self.saveDataToRealm()
}
}
@IBAction func previosQuestion(_ sender: Any) {
self.currentPage = self.currentPage - 1
self.weaklyRecordTableView.reloadData()
}
@IBAction func menuPage(_ sender: Any) {
self.navigationController?.popToRootViewController(animated: true)
//回菜單
}
func saveDataToRealm(){
var useVoiceTotal:Double = 0
var personAssessTotal:Double = 0
var useVoiceResult = ""
for i in 0...6{
useVoiceResult = useVoiceResult + String(VoiceDataManager.sharedInstance.weekUseVoice[i])
//4 = 沒有題目
if VoiceDataManager.sharedInstance.weekUseVoice[i] == 4 {
VoiceDataManager.sharedInstance.weekUseVoice[i] = 3
}else{
useVoiceTotal = useVoiceTotal + Double(VoiceDataManager.sharedInstance.weekUseVoice[i])
}
}
print(useVoiceTotal)
print(useVoiceResult)
for i in 0...9{
personAssessTotal = personAssessTotal + Double(VoiceDataManager.sharedInstance.weekPersonAssess[i])
}
RealmDataManager.shareInstance.addData(DateFormaterModel.shareInstance.timeStampToString(DateFormaterModel.shareInstance.getTimeStampSinceNow(num: 0), formatter: "YYYY-MM-dd"), useVoiceTotal: useVoiceTotal, personAssessResult: personAssessTotal, month: DateFormaterModel.shareInstance.timeStampToString(DateFormaterModel.shareInstance.getTimeStampSinceNow(num: 0), formatter: "YYYY-MM-dd"), useVoiceResult: useVoiceResult)
// GoogleDriveManager.sharedInstanse.createFile("test", fileTitle: "aaa", fileContent: "aaa")
}
}
//
// weekResult.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/8/3.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import RealmSwift
class weekResult: CustomNaviBar, UITableViewDelegate, UITableViewDataSource {
@IBOutlet weak var backNaviBtn: UIBarButtonItem!
@IBOutlet weak var resultTableView: UITableView!
var realmData:Results<WeekPracticeData>?
var selectCell:Int?
override func viewDidLoad() {
super.viewDidLoad()
resultTableView.delegate = self
resultTableView.dataSource = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.realmData = RealmDataManager.shareInstance.getAllData()
if self.realmData?.count == 0 {
AlertControllerModel.shareInstance.actionAlert(title: "無紀錄", massage: "尚未有紀錄", btnTitle: "確定", controller: self) { (Void) in
self.navigationController?.popViewController(animated: true)
}
}
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return realmData!.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell:weekResultTableViewCell = self.resultTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! weekResultTableViewCell
cell.cellView.layer.cornerRadius = 20
cell.lable.text = realmData?[indexPath.row].month
return cell
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
self.selectCell = indexPath.row
self.performSegue(withIdentifier: "toWeekDetail", sender: self)
}
@IBAction func backNaviBtn(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toWeekDetail"{
let vc = segue.destination as! weekResultDetailVC
vc.weekResultPoint = self.realmData?[self.selectCell!].useVoiceResult as! String
vc.personAccessDouble = self.realmData?[self.selectCell!].personAssessResult
vc.weekUseVoiceDouble = self.realmData?[self.selectCell!].useVoiceTotal
}
}
}
//
// weekResultDetailVC.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/8/4.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class weekResultDetailVC: CustomNaviBar, UITableViewDataSource, UITableViewDelegate {
@IBOutlet weak var resultTableView: UITableView!
@IBOutlet weak var weekUseVoice: UILabel!
@IBOutlet weak var personAccess: UILabel!
var weekResultPoint:String?
var array:[String]?
var weekUseVoiceDouble: Double?
var personAccessDouble: Double?
override func viewDidLoad() {
super.viewDidLoad()
resultTableView.delegate = self
resultTableView.dataSource = self
// Do any additional setup after loading the view.
}
override func viewWillAppear(_ animated: Bool) {
self.weekUseVoice.text = String(format: "%.0f", weekUseVoiceDouble!)
self.personAccess.text = String(format: "%.0f", personAccessDouble!)
}
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return 7
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = resultTableView.dequeueReusableCell(withIdentifier: "cell", for: indexPath) as! weekResultDetailCell
let char = weekResultPoint?.index(weekResultPoint!.startIndex, offsetBy: indexPath.row)
let string = String(weekResultPoint![char!])
if string == "4"{
cell.label.text = "無"
}else{
cell.label.text = string
}
return cell
}
@IBAction func backNaviBtn(_ sender: Any) {
self.navigationController?.popViewController(animated: true)
}
}
//
// AlertControllerModel.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/21.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class AlertControllerModel: NSObject {
static let shareInstance = AlertControllerModel()
func actionAlert(title: String, massage: String, btnTitle: String, controller: UIViewController, actionCompletion: ((Bool)->Void)?){
let alertController = UIAlertController(title: title, message: massage, preferredStyle: UIAlertController.Style.alert)
let confirmAction = UIAlertAction(title: btnTitle, style: .default) { (UIAlertAction)->Void in
actionCompletion?(true)
}
alertController.addAction(confirmAction)
controller.present(alertController, animated: true, completion: nil)
}
}
//
// DailyCell.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/6.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class DailyCell: UICollectionViewCell {
@IBOutlet weak var titleLabel: UILabel!
@IBOutlet weak var practiceImage: UIImageView!
override func awakeFromNib() {
}
}
//
// DashBoardTableViewCell.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/2.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class DashBoardTableViewCell: UITableViewCell {
@IBOutlet weak var itemLabel: UILabel!
@IBOutlet weak var itemImage: UIImageView!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
//
// PersonSetCell.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/31.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class PersonSetCell: UICollectionViewCell {
@IBOutlet weak var label: UILabel!
override func awakeFromNib() {
}
}
//
// weaklyRecordTableCell.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/14.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class weaklyRecordTableCell: UITableViewCell {
@IBOutlet weak var pickIcon: UIImageView!
@IBOutlet weak var answer: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
//
// weekResultDetailCell.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/8/4.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class weekResultDetailCell: UITableViewCell {
@IBOutlet weak var view: UIView!
@IBOutlet weak var label: UILabel!
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
//
// weekResultTableViewCell.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/8/4.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class weekResultTableViewCell: UITableViewCell {
@IBOutlet weak var lable: UILabel!
@IBOutlet weak var cellView: UIView!
override func awakeFromNib() {
super.awakeFromNib()
cellView.layer.cornerRadius = 20
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
}
//
// CustomNaviBar.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/3.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class CustomNaviBar: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.navigationItem.hidesBackButton = true
let rightBtn = UIButton.init(frame: CGRect(x: 0, y: 0, width: 23, height: 22))
rightBtn.setBackgroundImage(UIImage.init(named: "hamburger_button"), for: UIControl.State())
rightBtn.addTarget(self, action: #selector(self.navRightBtnAction), for: UIControl.Event.touchUpInside)
self.navigationItem.rightBarButtonItem = UIBarButtonItem.init(customView: rightBtn)
// Do any additional setup after loading the view.
}
@objc func navRightBtnAction(){
self.navigationController?.popToRootViewController(animated: true)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
//
// DateFormaterModel.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/8.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class DateFormaterModel: NSObject {
static let shareInstance = DateFormaterModel()
fileprivate override init() {
}
func timeStampToString(_ timeStamp : String , formatter : String)->String {
let string = NSString(string: timeStamp)
let timeSta:TimeInterval = string.doubleValue
let dfmatter = DateFormatter()
dfmatter.dateFormat = formatter
let date = Date(timeIntervalSince1970: timeSta)
return dfmatter.string(from: date)
}
func getTimeStampSinceNow(num: Int) -> String{
let now = Date.init()
let date = Date.init(timeInterval: 86400 * Double(num), since: now)
let dateStamp:TimeInterval = date.timeIntervalSince1970
let dateStr:Int = Int(dateStamp)
return String(dateStr)
}
func dateToSting(date:Date,format:String) -> String{
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = format
return dateFormatter.string(from: date)
}
}
//
// GoogleDriveManager.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/8/6.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import GoogleSignIn
import GoogleAPIClientForREST
import GTMSessionFetcher
class GoogleDriveManager: NSObject {
var scopes = [kGTLRAuthScopeDrive]
static let sharedInstanse = GoogleDriveManager()
var uploadFolderID: String?
var folderID : String?
let service = GTLRDriveService()
func getFolderID(service: GTLRDriveService, completion: @escaping(String?) -> Void){
let query = GTLRDriveQuery_FilesList.query()
query.pageSize = 100
query.corpora = "user"
query.q = "name = 'test'"
service.executeQuery(query) { (_, result, error) in
guard error == nil else{
fatalError(error!.localizedDescription)
}
let folderList = result as! GTLRDrive_FileList
self.folderID = folderList.files?.first?.identifier
print(self.folderID)
// self.createFile("test", fileTitle: "test.txt", fileContent: "aaa")
}
}
// func createFile(_ folderName: String, fileTitle: String, fileContent: String){
// if let parentId = self.folderID{
// let name = "\(fileTitle)"
// let content = "\(fileTitle)"
// let mimeType = "text/csv"
//
// let metadata = GTLRDrive_File()
// metadata.name = name
// metadata.parents = [parentId]
//
// let data = content.data(using: String.Encoding.utf8)
// let uploadPatameters = GTLRUploadParameters()
// uploadPatameters.data = data
// uploadPatameters.mimeType = mimeType
// let query = GTLRDriveQuery_FilesCreate.query(withObject: metadata, uploadParameters: uploadPatameters)
// self.service.executeQuery(query) { (ticket, updataFile, error) in
// if (error == nil){
// print("file \(updataFile)")
// }else{
// print("error : \(error)")
// }
// }
// }
// }
// func createFloder(name: String, service: GTLRDriveService, completion: @escaping(String) -> Void){
// let folder = GTLRDrive_File()
// folder.mimeType = "application/vnd.google-apps.folder"
// folder.name = name
//
// let query = GTLRDriveQuery_FilesCreate.query(withObject: folder, uploadParameters: nil)
//
// self.service.executeQuery(query) { (_, file, error) in
// guard error == nil else{
// fatalError(error!.localizedDescription)
// }
// let folder = file as! GTLRDrive_File
// completion(folder.identifier!)
// }
// }
}
//
// LocalNotificationManager.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/28.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import UserNotifications
class LocalNotificationManager: NSObject {
static let shareInstanse = LocalNotificationManager()
let notificationCenter = UNUserNotificationCenter.current()
var contentBadge:NSNumber = 0
func createLocalNotification(setdate:Date, identifier:String){
let content = UNMutableNotificationContent()
content.title = "每日練習提醒"
content.body = "每日練習提醒"
content.sound = UNNotificationSound.default
let triggerDaily = Calendar.current.dateComponents([.hour, .minute, .second], from: setdate)
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerDaily, repeats: true)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
print("added")
notificationCenter.add(request) { (error) in
if let error = error{
print("\(error.localizedDescription)")
}
}
}
func createWeeklyLocalNotification(setdate:String, identifier:String){
let dateFormatter = DateFormatter()
dateFormatter.dateFormat = "EEEEHH:mm"
dateFormatter.locale = Locale(identifier: "zh_TW")
let date = dateFormatter.date(from: setdate)
let triggerWeekly = Calendar.current.dateComponents([.weekday, .hour, .minute , .second], from: date!)
let content = UNMutableNotificationContent()
content.title = "每週評量提醒"
content.body = "每週評量提醒"
content.sound = UNNotificationSound.default
let trigger = UNCalendarNotificationTrigger(dateMatching: triggerWeekly, repeats: true)
let request = UNNotificationRequest(identifier: identifier, content: content, trigger: trigger)
print("added")
notificationCenter.add(request) { (error) in
if let error = error{
print("\(error.localizedDescription)")
}
}
}
func cancelNotification(identifier:String){
notificationCenter.removePendingNotificationRequests(withIdentifiers: [identifier])
notificationCenter.removeDeliveredNotifications(withIdentifiers: [identifier])
}
}
//
// RealmDataManager.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/20.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import RealmSwift
class RealmDataManager: NSObject {
static let shareInstance = RealmDataManager()
let realm = try! Realm()
func addData(_ timeStamp: String, useVoiceTotal:Double, personAssessResult: Double, month: String, useVoiceResult:String){
let weekPacticeData = WeekPracticeData()
weekPacticeData.timeStamp = timeStamp
weekPacticeData.useVoiceTotal = useVoiceTotal
weekPacticeData.personAssessResult = personAssessResult
weekPacticeData.month = month
weekPacticeData.useVoiceResult = useVoiceResult
try! realm.write({ (realm.add(weekPacticeData, update: .all))
})
print("saved")
}
func getAllData() -> Results<WeekPracticeData>{
return realm.objects(WeekPracticeData.self).sorted(byKeyPath: "timeStamp", ascending: true)
}
}
//
// RealmWeekPracticeData.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/20.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
import RealmSwift
class WeekPracticeData: Object{
@objc dynamic var timeStamp = ""
@objc dynamic var useVoiceTotal:Double = 0
@objc dynamic var personAssessResult:Double = 0
@objc dynamic var month = ""
@objc dynamic var useVoiceResult = ""
override static func primaryKey() -> String? {
return "timeStamp"
}
}
//
// VoiceDataManager.swift
// VoicePractice
//
// Created by Wei Xian Ong on 2020/7/8.
// Copyright © 2020 Wei Xian Ong. All rights reserved.
//
import UIKit
class VoiceDataManager: NSObject {
static let sharedInstance = VoiceDataManager()
var dailyStateAry = [Bool]()
//每日練習題目
let dailyTitleAry = ["放鬆運動練習","腹式呼吸練習","吹水發聲練習","高低音吟唱練習","前置共鳴練習","軟起聲練習"]
//每日練習圖片
let dailyImageAry = ["Practice4_icon","Practice1_icon","Practice2_icon","Practice3_icon","Practice5_icon","Practice6_icon"]
//題目的GIF(2,3,4題)
let dailyGifAry = ["breathing","sing_in_water","musicnotes"]
//第四題的輪播圖片
let relaxParcImageAry = [UIImage.init(named: "Practice4_actionA_icon")! , UIImage.init(named: "Practice4_action B_icon")! , UIImage.init(named: "Practice4_action C_icon")! , UIImage.init(named: "Practice4_action_D_icon")!]
//每日練習ㄇㄏ輪播的字的image
let muWordImageAry = [UIImage.init(named: "cat")! , UIImage.init(named: "cotton")! , UIImage.init(named: "tomorrow")! , UIImage.init(named: "rose")! , UIImage.init(named: "wood")! , UIImage.init(named: "monlong")! , UIImage.init(named: "slow")! , UIImage.init(named: "okay")! , UIImage.init(named: "cottonSwab")! , UIImage.init(named: "eye")! , UIImage.init(named: "catSoup")! , UIImage.init(named: "beautyLife")!]
let muWordVoice = ["貓咪" , "棉花" , "明天" , "玫瑰" , "木頭" , "朦朧" , "慢慢來" , "沒關係" , "棉花棒" , "瞇瞇眼" , "貓咪喝湯" , "美麗人生"]
let herWordImageAry = [UIImage.init(named: "breath")! , UIImage.init(named: "flower")! , UIImage.init(named: "peace")! , UIImage.init(named: "ha")! , UIImage.init(named: "butterfly")! , UIImage.init(named: "river")! , UIImage.init(named: "laugh")! , UIImage.init(named: "draw")! , UIImage.init(named: "redLight")! , UIImage.init(named: "goHome")! , UIImage.init(named: "save")! , UIImage.init(named: "happy")!]
let herWordVoice = ["呼吸","花朵","和平","哈欠","蝴蝶","河流","哈哈笑","畫畫圖","紅綠燈","回家好","闔家平安","和樂融融"]
var weekUseVoice:[Int] = [4,4,4,4,4,4,4]
let weekUseVoiceDic = [
0 : ["第一題","不適當的說話行為\n如大叫、壓低音、\n連續說話(超過20分鐘)","常常","有時候","很少","幾乎沒有"],
1 : ["第二題","一天說話的量","完全沒說話","有說幾句","說一些","說(或唱)很多"] ,
2 : ["第三題","清喉嚨的次數","常常","有時候","很少","幾乎沒有"] ,
3 : ["第四題","喝濃茶/咖啡的行為(平均每天)","超過3杯","1杯","2~3杯","幾乎沒有"] ,
4 : ["第五題","抽菸的行為\n(平均每天)","1包或更多","半包左右","1~5根","幾乎沒有"] ,
5 : ["第六題","喝水的頻率\n(平均每天)","整天只喝一兩次或更少","半天喝一次","每1小時喝一次","每半小時喝一次"] ,
6 : ["第七題","嗓音練習次數","0次","1次","2次","3次"]
]
var weekPersonAssess:[Int] = [0,0,0,0,0,0,0,0,0,0]
let weekPersonAssessDic = [
0 : ["第一題","我的嗓音\n很難讓人聽清楚","總是如此","經常","有時候","很少","從來沒有"],
1 : ["第二題","在吵雜室內\n別人很難聽懂我說什麼","總是如此","經常","有時候","很少","從來沒有"] ,
2 : ["第三題","嗓音問題限制了\n我的個人與社交行為","總是如此","經常","有時候","很少","從來沒有"] ,
3 : ["第四題","嗓音問題\n使我無法與人順利交談","總是如此","經常","有時候","很少","從來沒有"] ,
4 : ["第五題","發出聲音或是說話\n讓我覺得很吃力","總是如此","經常","有時候","很少","從來沒有"] ,
5 : ["第六題","我無法預測\n什麼時候嗓音會清楚","總是如此","經常","有時候","很少","從來沒有"] ,
6 : ["第七題","我的嗓音問題\n讓我很困擾","總是如此","經常","有時候","很少","從來沒有"] ,
7 : ["第八題","我的嗓音問題\n使我覺得有障礙","總是如此","經常","有時候","很少","從來沒有"] ,
8 : ["第九題","別人常問\n你的聲音怎麼了","總是如此","經常","有時候","很少","從來沒有"] ,
9 : ["第十題","我的聲音問題\n影響我了我的收入","總是如此","經常","有時候","很少","從來沒有"]
]
}
# Uncomment the next line to define a global platform for your project
# platform :ios, '9.0'
target 'VoicePractice' do
# Comment the next line if you don't want to use dynamic frameworks
use_frameworks!
pod 'GoogleSignIn', '~> 4.4.0'
pod 'GoogleAPIClientForREST/Drive', '~> 1.3.7'
# Pods for VoicePractice
target 'VoicePracticeTests' do
inherit! :search_paths
# Pods for testing
end
target 'VoicePracticeUITests' do
# Pods for testing
end
end
PODS:
- GoogleAPIClientForREST/Core (1.3.11):
- GTMSessionFetcher (>= 1.1.7)
- GoogleAPIClientForREST/Drive (1.3.11):
- GoogleAPIClientForREST/Core
- GTMSessionFetcher (>= 1.1.7)
- GoogleSignIn (4.4.0):
- "GoogleToolboxForMac/NSDictionary+URLArguments (~> 2.1)"
- "GoogleToolboxForMac/NSString+URLArguments (~> 2.1)"
- GTMSessionFetcher/Core (~> 1.1)
- GoogleToolboxForMac/DebugUtils (2.2.2):
- GoogleToolboxForMac/Defines (= 2.2.2)
- GoogleToolboxForMac/Defines (2.2.2)
- "GoogleToolboxForMac/NSDictionary+URLArguments (2.2.2)":
- GoogleToolboxForMac/DebugUtils (= 2.2.2)
- GoogleToolboxForMac/Defines (= 2.2.2)
- "GoogleToolboxForMac/NSString+URLArguments (= 2.2.2)"
- "GoogleToolboxForMac/NSString+URLArguments (2.2.2)"
- GTMSessionFetcher (1.4.0):
- GTMSessionFetcher/Full (= 1.4.0)
- GTMSessionFetcher/Core (1.4.0)
- GTMSessionFetcher/Full (1.4.0):
- GTMSessionFetcher/Core (= 1.4.0)
DEPENDENCIES:
- GoogleAPIClientForREST/Drive (~> 1.3.7)
- GoogleSignIn (~> 4.4.0)
SPEC REPOS:
trunk:
- GoogleAPIClientForREST
- GoogleSignIn
- GoogleToolboxForMac
- GTMSessionFetcher
SPEC CHECKSUMS:
GoogleAPIClientForREST: 0f19a8280dfe6471f76016645d26eb5dae305101
GoogleSignIn: 7ff245e1a7b26d379099d3243a562f5747e23d39
GoogleToolboxForMac: 800648f8b3127618c1b59c7f97684427630c5ea3
GTMSessionFetcher: 6f5c8abbab8a9bce4bb3f057e317728ec6182b10
PODFILE CHECKSUM: c32e7ccad25be362b6ecbe8904deed1c595ef93a
COCOAPODS: 1.9.1
# Google Toolbox for Mac - Session Fetcher #
**Project site** <https://github.com/google/gtm-session-fetcher><br>
**Discussion group** <http://groups.google.com/group/google-toolbox-for-mac>
[![Build Status](https://travis-ci.org/google/gtm-session-fetcher.svg?branch=master)](https://travis-ci.org/google/gtm-session-fetcher)
`GTMSessionFetcher` makes it easy for Cocoa applications to perform http
operations. The fetcher is implemented as a wrapper on `NSURLSession`, so its
behavior is asynchronous and uses operating-system settings on iOS and Mac OS X.
Features include:
- Simple to build; only one source/header file pair is required
- Simple to use: takes just two lines of code to fetch a request
- Supports upload and download sessions
- Flexible cookie storage
- Automatic retry on errors, with exponential backoff
- Support for generating multipart MIME upload streams
- Easy, convenient logging of http requests and responses
- Supports plug-in authentication such as with GTMAppAuth
- Easily testable; self-mocking
- Automatic rate limiting when created by the `GTMSessionFetcherService` factory class
- Fully independent of other projects
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// The GTMGatherInput stream is an input stream implementation that is to be
// instantiated with an NSArray of NSData objects. It works in the traditional
// scatter/gather vector I/O model. Rather than allocating a big NSData object
// to hold all of the data and performing a copy into that object, the
// GTMGatherInputStream will maintain a reference to the NSArray and read from
// each NSData in turn as the read method is called. You should not alter the
// underlying set of NSData objects until all read operations on this input
// stream have completed.
#import <Foundation/Foundation.h>
#ifndef GTM_NONNULL
#if defined(__has_attribute)
#if __has_attribute(nonnull)
#define GTM_NONNULL(x) __attribute__((nonnull x))
#else
#define GTM_NONNULL(x)
#endif
#else
#define GTM_NONNULL(x)
#endif
#endif
// Avoid multiple declaration of this class.
//
// Note: This should match the declaration of GTMGatherInputStream in GTMMIMEDocument.m
#ifndef GTM_GATHERINPUTSTREAM_DECLARED
#define GTM_GATHERINPUTSTREAM_DECLARED
@interface GTMGatherInputStream : NSInputStream <NSStreamDelegate>
+ (NSInputStream *)streamWithArray:(NSArray *)dataArray GTM_NONNULL((1));
@end
#endif // GTM_GATHERINPUTSTREAM_DECLARED
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GTMGatherInputStream.h"
@implementation GTMGatherInputStream {
NSArray *_dataArray; // NSDatas that should be "gathered" and streamed.
NSUInteger _arrayIndex; // Index in the array of the current NSData.
long long _dataOffset; // Offset in the current NSData we are processing.
NSStreamStatus _streamStatus;
id<NSStreamDelegate> __weak _delegate; // Stream delegate, defaults to self.
}
+ (NSInputStream *)streamWithArray:(NSArray *)dataArray {
return [(GTMGatherInputStream *)[self alloc] initWithArray:dataArray];
}
- (instancetype)initWithArray:(NSArray *)dataArray {
self = [super init];
if (self) {
_dataArray = dataArray;
_delegate = self; // An NSStream's default delegate should be self.
}
return self;
}
#pragma mark - NSStream
- (void)open {
_arrayIndex = 0;
_dataOffset = 0;
_streamStatus = NSStreamStatusOpen;
}
- (void)close {
_streamStatus = NSStreamStatusClosed;
}
- (id<NSStreamDelegate>)delegate {
return _delegate;
}
- (void)setDelegate:(id<NSStreamDelegate>)delegate {
if (delegate == nil) {
_delegate = self;
} else {
_delegate = delegate;
}
}
- (id)propertyForKey:(NSString *)key {
if ([key isEqual:NSStreamFileCurrentOffsetKey]) {
return @([self absoluteOffset]);
}
return nil;
}
- (BOOL)setProperty:(id)property forKey:(NSString *)key {
if ([key isEqual:NSStreamFileCurrentOffsetKey]) {
NSNumber *absoluteOffsetNumber = property;
[self setAbsoluteOffset:absoluteOffsetNumber.longLongValue];
return YES;
}
return NO;
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
}
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
}
- (NSStreamStatus)streamStatus {
return _streamStatus;
}
- (NSError *)streamError {
return nil;
}
#pragma mark - NSInputStream
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
NSInteger bytesRead = 0;
NSUInteger bytesRemaining = len;
// Read bytes from the currently-indexed array.
while ((bytesRemaining > 0) && (_arrayIndex < _dataArray.count)) {
NSData *data = [_dataArray objectAtIndex:_arrayIndex];
NSUInteger dataLen = data.length;
NSUInteger dataBytesLeft = dataLen - (NSUInteger)_dataOffset;
NSUInteger bytesToCopy = MIN(bytesRemaining, dataBytesLeft);
NSRange range = NSMakeRange((NSUInteger) _dataOffset, bytesToCopy);
[data getBytes:(buffer + bytesRead) range:range];
bytesRead += bytesToCopy;
_dataOffset += bytesToCopy;
bytesRemaining -= bytesToCopy;
if (_dataOffset == (long long)dataLen) {
_dataOffset = 0;
_arrayIndex++;
}
}
if (_arrayIndex >= _dataArray.count) {
_streamStatus = NSStreamStatusAtEnd;
}
return bytesRead;
}
- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len {
return NO; // We don't support this style of reading.
}
- (BOOL)hasBytesAvailable {
// If we return no, the read never finishes, even if we've already delivered all the bytes.
return YES;
}
#pragma mark - NSStreamDelegate
- (void)stream:(NSStream *)theStream handleEvent:(NSStreamEvent)streamEvent {
id<NSStreamDelegate> delegate = _delegate;
if (delegate != self) {
[delegate stream:self handleEvent:streamEvent];
}
}
#pragma mark - Private
- (long long)absoluteOffset {
long long absoluteOffset = 0;
NSUInteger index = 0;
for (NSData *data in _dataArray) {
if (index >= _arrayIndex) {
break;
}
absoluteOffset += data.length;
++index;
}
absoluteOffset += _dataOffset;
return absoluteOffset;
}
- (void)setAbsoluteOffset:(long long)absoluteOffset {
if (absoluteOffset < 0) {
absoluteOffset = 0;
}
_arrayIndex = 0;
_dataOffset = absoluteOffset;
for (NSData *data in _dataArray) {
long long dataLen = (long long) data.length;
if (dataLen > _dataOffset) {
break;
}
_arrayIndex++;
_dataOffset -= dataLen;
}
if (_arrayIndex == _dataArray.count) {
if (_dataOffset > 0) {
_dataOffset = 0;
}
}
}
@end
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// This is a simple class to create or parse a MIME document.
// To create a MIME document, allocate a new GTMMIMEDocument and start adding parts.
// When you are done adding parts, call generateInputStream or generateDispatchData.
//
// A good reference for MIME is http://en.wikipedia.org/wiki/MIME
#import <Foundation/Foundation.h>
#ifndef GTM_NONNULL
#if defined(__has_attribute)
#if __has_attribute(nonnull)
#define GTM_NONNULL(x) __attribute__((nonnull x))
#else
#define GTM_NONNULL(x)
#endif
#else
#define GTM_NONNULL(x)
#endif
#endif
#ifndef GTM_DECLARE_GENERICS
#if __has_feature(objc_generics)
#define GTM_DECLARE_GENERICS 1
#else
#define GTM_DECLARE_GENERICS 0
#endif
#endif
#ifndef GTM_NSArrayOf
#if GTM_DECLARE_GENERICS
#define GTM_NSArrayOf(value) NSArray<value>
#define GTM_NSDictionaryOf(key, value) NSDictionary<key, value>
#else
#define GTM_NSArrayOf(value) NSArray
#define GTM_NSDictionaryOf(key, value) NSDictionary
#endif // GTM_DECLARE_GENERICS
#endif // GTM_NSArrayOf
// GTMMIMEDocumentPart represents a part of a MIME document.
//
// +[GTMMIMEDocument MIMEPartsWithBoundary:data:] returns an array of these.
@interface GTMMIMEDocumentPart : NSObject
@property(nonatomic, readonly) GTM_NSDictionaryOf(NSString *, NSString *) *headers;
@property(nonatomic, readonly) NSData *headerData;
@property(nonatomic, readonly) NSData *body;
@property(nonatomic, readonly) NSUInteger length;
+ (instancetype)partWithHeaders:(NSDictionary *)headers body:(NSData *)body;
@end
@interface GTMMIMEDocument : NSObject
// Get or set the unique boundary for the parts that have been added.
//
// When creating a MIME document from parts, this is typically calculated
// automatically after all parts have been added.
@property(nonatomic, copy) NSString *boundary;
#pragma mark - Methods for Creating a MIME Document
+ (instancetype)MIMEDocument;
// Adds a new part to this mime document with the given headers and body.
// The headers keys and values should be NSStrings.
// Adding a part may cause the boundary string to change.
- (void)addPartWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers
body:(NSData *)body GTM_NONNULL((1,2));
// An inputstream that can be used to efficiently read the contents of the MIME document.
//
// Any parameter may be null if the result is not wanted.
- (void)generateInputStream:(NSInputStream **)outStream
length:(unsigned long long *)outLength
boundary:(NSString **)outBoundary;
// A dispatch_data_t with the contents of the MIME document.
//
// Note: dispatch_data_t is one-way toll-free bridged so the result
// may be cast directly to NSData *.
//
// Any parameter may be null if the result is not wanted.
- (void)generateDispatchData:(dispatch_data_t *)outDispatchData
length:(unsigned long long *)outLength
boundary:(NSString **)outBoundary;
// Utility method for making a header section, including trailing newlines.
+ (NSData *)dataWithHeaders:(GTM_NSDictionaryOf(NSString *, NSString *) *)headers;
#pragma mark - Methods for Parsing a MIME Document
// Method for parsing out an array of MIME parts from a MIME document.
//
// Returns an array of GTMMIMEDocumentParts. Returns nil if no part can
// be found.
+ (GTM_NSArrayOf(GTMMIMEDocumentPart *) *)MIMEPartsWithBoundary:(NSString *)boundary
data:(NSData *)fullDocumentData;
// Utility method for efficiently searching possibly discontiguous NSData
// for occurrences of target byte. This method does not "flatten" an NSData
// that is composed of discontiguous blocks.
//
// The byte offsets of non-overlapping occurrences of the target are returned as
// NSNumbers in the array.
+ (void)searchData:(NSData *)data
targetBytes:(const void *)targetBytes
targetLength:(NSUInteger)targetLength
foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets;
// Utility method to parse header bytes into an NSDictionary.
+ (GTM_NSDictionaryOf(NSString *, NSString *) *)headersWithData:(NSData *)data;
// ------ UNIT TESTING ONLY BELOW ------
// Internal methods, exposed for unit testing only.
- (void)seedRandomWith:(u_int32_t)seed;
+ (NSUInteger)findBytesWithNeedle:(const unsigned char *)needle
needleLength:(NSUInteger)needleLength
haystack:(const unsigned char *)haystack
haystackLength:(NSUInteger)haystackLength
foundOffset:(NSUInteger *)foundOffset;
+ (void)searchData:(NSData *)data
targetBytes:(const void *)targetBytes
targetLength:(NSUInteger)targetLength
foundOffsets:(GTM_NSArrayOf(NSNumber *) **)outFoundOffsets
foundBlockNumbers:(GTM_NSArrayOf(NSNumber *) **)outFoundBlockNumbers;
@end
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#ifndef GTM_NONNULL
#if defined(__has_attribute)
#if __has_attribute(nonnull)
#define GTM_NONNULL(x) __attribute__((nonnull x))
#else
#define GTM_NONNULL(x)
#endif
#else
#define GTM_NONNULL(x)
#endif
#endif
@interface GTMReadMonitorInputStream : NSInputStream <NSStreamDelegate>
+ (instancetype)inputStreamWithStream:(NSInputStream *)input GTM_NONNULL((1));
- (instancetype)initWithStream:(NSInputStream *)input GTM_NONNULL((1));
// The read monitor selector is called when bytes have been read. It should have this signature:
//
// - (void)inputStream:(GTMReadMonitorInputStream *)stream
// readIntoBuffer:(uint8_t *)buffer
// length:(int64_t)length;
@property(atomic, weak) id readDelegate;
@property(atomic, assign) SEL readSelector;
// Modes for invoking callbacks, when necessary.
@property(atomic, strong) NSArray *runLoopModes;
@end
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "GTMReadMonitorInputStream.h"
@implementation GTMReadMonitorInputStream {
NSInputStream *_inputStream; // Encapsulated stream that does the work.
NSThread *_thread; // Thread in which this object was created.
NSArray *_runLoopModes; // Modes for calling callbacks, when necessary.
}
@synthesize readDelegate = _readDelegate;
@synthesize readSelector = _readSelector;
@synthesize runLoopModes = _runLoopModes;
// We'll forward all unhandled messages to the NSInputStream class or to the encapsulated input
// stream. This is needed for all messages sent to NSInputStream which aren't handled by our
// superclass; that includes various private run loop calls.
+ (NSMethodSignature *)methodSignatureForSelector:(SEL)selector {
return [NSInputStream methodSignatureForSelector:selector];
}
+ (void)forwardInvocation:(NSInvocation*)invocation {
[invocation invokeWithTarget:[NSInputStream class]];
}
- (BOOL)respondsToSelector:(SEL)selector {
return [_inputStream respondsToSelector:selector];
}
- (NSMethodSignature*)methodSignatureForSelector:(SEL)selector {
return [_inputStream methodSignatureForSelector:selector];
}
- (void)forwardInvocation:(NSInvocation*)invocation {
[invocation invokeWithTarget:_inputStream];
}
#pragma mark -
+ (instancetype)inputStreamWithStream:(NSInputStream *)input {
return [[self alloc] initWithStream:input];
}
- (instancetype)initWithStream:(NSInputStream *)input {
self = [super init];
if (self) {
_inputStream = input;
_thread = [NSThread currentThread];
}
return self;
}
- (instancetype)init {
[self doesNotRecognizeSelector:_cmd];
return nil;
}
#pragma mark -
- (NSInteger)read:(uint8_t *)buffer maxLength:(NSUInteger)len {
// Read from the encapsulated stream.
NSInteger numRead = [_inputStream read:buffer maxLength:len];
if (numRead > 0) {
if (_readDelegate && _readSelector) {
// Call the read selector with the buffer and number of bytes actually read into it.
BOOL isOnOriginalThread = [_thread isEqual:[NSThread currentThread]];
if (isOnOriginalThread) {
// Invoke immediately.
NSData *data = [NSData dataWithBytesNoCopy:buffer
length:(NSUInteger)numRead
freeWhenDone:NO];
[self invokeReadSelectorWithBuffer:data];
} else {
// Copy the buffer into an NSData to be retained by the performSelector,
// and invoke on the proper thread.
SEL sel = @selector(invokeReadSelectorWithBuffer:);
NSData *data = [NSData dataWithBytes:buffer length:(NSUInteger)numRead];
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Warc-performSelector-leaks"
if (_runLoopModes) {
[self performSelector:sel
onThread:_thread
withObject:data
waitUntilDone:NO
modes:_runLoopModes];
} else {
[self performSelector:sel
onThread:_thread
withObject:data
waitUntilDone:NO];
}
#pragma clang diagnostic pop
}
}
}
return numRead;
}
- (void)invokeReadSelectorWithBuffer:(NSData *)data {
const void *buffer = data.bytes;
int64_t length = (int64_t)data.length;
id argSelf = self;
id readDelegate = _readDelegate;
if (readDelegate) {
NSMethodSignature *signature = [readDelegate methodSignatureForSelector:_readSelector];
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:signature];
[invocation setSelector:_readSelector];
[invocation setTarget:readDelegate];
[invocation setArgument:&argSelf atIndex:2];
[invocation setArgument:&buffer atIndex:3];
[invocation setArgument:&length atIndex:4];
[invocation invoke];
}
}
- (BOOL)getBuffer:(uint8_t **)buffer length:(NSUInteger *)len {
return [_inputStream getBuffer:buffer length:len];
}
- (BOOL)hasBytesAvailable {
return [_inputStream hasBytesAvailable];
}
#pragma mark Standard messages
// Pass expected messages to our encapsulated stream.
//
// We want our encapsulated NSInputStream to handle the standard messages;
// we don't want the superclass to handle them.
- (void)open {
[_inputStream open];
}
- (void)close {
[_inputStream close];
}
- (id)delegate {
return [_inputStream delegate];
}
- (void)setDelegate:(id)delegate {
[_inputStream setDelegate:delegate];
}
- (id)propertyForKey:(NSString *)key {
return [_inputStream propertyForKey:key];
}
- (BOOL)setProperty:(id)property forKey:(NSString *)key {
return [_inputStream setProperty:property forKey:key];
}
- (void)scheduleInRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
[_inputStream scheduleInRunLoop:aRunLoop forMode:mode];
}
- (void)removeFromRunLoop:(NSRunLoop *)aRunLoop forMode:(NSString *)mode {
[_inputStream removeFromRunLoop:aRunLoop forMode:mode];
}
- (NSStreamStatus)streamStatus {
return [_inputStream streamStatus];
}
- (NSError *)streamError {
return [_inputStream streamError];
}
@end
This source diff could not be displayed because it is too large. You can view the blob instead.
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GTMSessionFetcher.h"
// GTM HTTP Logging
//
// All traffic using GTMSessionFetcher can be easily logged. Call
//
// [GTMSessionFetcher setLoggingEnabled:YES];
//
// to begin generating log files.
//
// Unless explicitly set by the application using +setLoggingDirectory:,
// logs are put into a default directory, located at:
// * macOS: ~/Desktop/GTMHTTPDebugLogs
// * iOS simulator: ~/GTMHTTPDebugLogs (in application sandbox)
// * iOS device: ~/Documents/GTMHTTPDebugLogs (in application sandbox)
//
// Tip: use the Finder's "Sort By Date" to find the most recent logs.
//
// Each run of an application gets a separate set of log files. An html
// file is generated to simplify browsing the run's http transactions.
// The html file includes javascript links for inline viewing of uploaded
// and downloaded data.
//
// A symlink is created in the logs folder to simplify finding the html file
// for the latest run of the application; the symlink is called
//
// AppName_http_log_newest.html
//
// For better viewing of XML logs, use Camino or Firefox rather than Safari.
//
// Each fetcher may be given a comment to be inserted as a label in the logs,
// such as
// [fetcher setCommentWithFormat:@"retrieve item %@", itemName];
//
// Projects may define STRIP_GTM_FETCH_LOGGING to remove logging code.
#if !STRIP_GTM_FETCH_LOGGING
@interface GTMSessionFetcher (GTMSessionFetcherLogging)
// Note: on macOS the default logs directory is ~/Desktop/GTMHTTPDebugLogs; on
// iOS simulators it will be the ~/GTMHTTPDebugLogs (in the app sandbox); on
// iOS devices it will be in ~/Documents/GTMHTTPDebugLogs (in the app sandbox).
// These directories will be created as needed, and are excluded from backups
// to iCloud and iTunes.
//
// If a custom directory is set, the directory should already exist. It is
// the application's responsibility to exclude any custom directory from
// backups, if desired.
+ (void)setLoggingDirectory:(NSString *)path;
+ (NSString *)loggingDirectory;
// client apps can turn logging on and off
+ (void)setLoggingEnabled:(BOOL)isLoggingEnabled;
+ (BOOL)isLoggingEnabled;
// client apps can turn off logging to a file if they want to only check
// the fetcher's log property
+ (void)setLoggingToFileEnabled:(BOOL)isLoggingToFileEnabled;
+ (BOOL)isLoggingToFileEnabled;
// client apps can optionally specify process name and date string used in
// log file names
+ (void)setLoggingProcessName:(NSString *)processName;
+ (NSString *)loggingProcessName;
+ (void)setLoggingDateStamp:(NSString *)dateStamp;
+ (NSString *)loggingDateStamp;
// client apps can specify the directory for the log for this specific run,
// typically to match the directory used by another fetcher class, like:
//
// [GTMSessionFetcher setLogDirectoryForCurrentRun:[GTMHTTPFetcher logDirectoryForCurrentRun]];
//
// Setting this overrides the logging directory, process name, and date stamp when writing
// the log file.
+ (void)setLogDirectoryForCurrentRun:(NSString *)logDirectoryForCurrentRun;
+ (NSString *)logDirectoryForCurrentRun;
// Prunes old log directories that have not been modified since the provided date.
// This will not delete the current run's log directory.
+ (void)deleteLogDirectoriesOlderThanDate:(NSDate *)date;
// internal; called by fetcher
- (void)logFetchWithError:(NSError *)error;
- (NSInputStream *)loggedInputStreamForInputStream:(NSInputStream *)inputStream;
- (GTMSessionFetcherBodyStreamProvider)loggedStreamProviderForStreamProvider:
(GTMSessionFetcherBodyStreamProvider)streamProvider;
// internal; accessors useful for viewing logs
+ (NSString *)processNameLogPrefix;
+ (NSString *)symlinkNameSuffix;
+ (NSString *)htmlFileName;
@end
#endif // !STRIP_GTM_FETCH_LOGGING
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// For best performance and convenient usage, fetchers should be generated by a common
// GTMSessionFetcherService instance, like
//
// _fetcherService = [[GTMSessionFetcherService alloc] init];
// GTMSessionFetcher* myFirstFetcher = [_fetcherService fetcherWithRequest:request1];
// GTMSessionFetcher* mySecondFetcher = [_fetcherService fetcherWithRequest:request2];
#import "GTMSessionFetcher.h"
GTM_ASSUME_NONNULL_BEGIN
// Notifications.
// This notification indicates a reusable session has become invalid. It is intended mainly for the
// service's unit tests.
//
// The notification object is the fetcher service.
// The invalid session is provided via the userInfo kGTMSessionFetcherServiceSessionKey key.
extern NSString *const kGTMSessionFetcherServiceSessionBecameInvalidNotification;
extern NSString *const kGTMSessionFetcherServiceSessionKey;
@interface GTMSessionFetcherService : NSObject<GTMSessionFetcherServiceProtocol>
// Queues of delayed and running fetchers. Each dictionary contains arrays
// of GTMSessionFetcher *fetchers, keyed by NSString *host
@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *delayedFetchersByHost;
@property(atomic, strong, readonly, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, NSArray *) *runningFetchersByHost;
// A max value of 0 means no fetchers should be delayed.
// The default limit is 10 simultaneous fetchers targeting each host.
// This does not apply to fetchers whose useBackgroundSession property is YES. Since services are
// not resurrected on an app relaunch, delayed fetchers would effectively be abandoned.
@property(atomic, assign) NSUInteger maxRunningFetchersPerHost;
// Properties to be applied to each fetcher; see GTMSessionFetcher.h for descriptions
@property(atomic, strong, GTM_NULLABLE) NSURLSessionConfiguration *configuration;
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherConfigurationBlock configurationBlock;
@property(atomic, strong, GTM_NULLABLE) NSHTTPCookieStorage *cookieStorage;
@property(atomic, strong, GTM_NULL_RESETTABLE) dispatch_queue_t callbackQueue;
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherChallengeBlock challengeBlock;
@property(atomic, strong, GTM_NULLABLE) NSURLCredential *credential;
@property(atomic, strong) NSURLCredential *proxyCredential;
@property(atomic, copy, GTM_NULLABLE) GTM_NSArrayOf(NSString *) *allowedInsecureSchemes;
@property(atomic, assign) BOOL allowLocalhostRequest;
@property(atomic, assign) BOOL allowInvalidServerCertificates;
@property(atomic, assign, getter=isRetryEnabled) BOOL retryEnabled;
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherRetryBlock retryBlock;
@property(atomic, assign) NSTimeInterval maxRetryInterval;
@property(atomic, assign) NSTimeInterval minRetryInterval;
@property(atomic, copy, GTM_NULLABLE) GTM_NSDictionaryOf(NSString *, id) *properties;
@property(atomic, copy, GTM_NULLABLE)
GTMSessionFetcherMetricsCollectionBlock metricsCollectionBlock API_AVAILABLE(
ios(10.0), macosx(10.12), tvos(10.0), watchos(3.0));
#if GTM_BACKGROUND_TASK_FETCHING
@property(atomic, assign) BOOL skipBackgroundTask;
#endif
// A default useragent of GTMFetcherStandardUserAgentString(nil) will be given to each fetcher
// created by this service unless the request already has a user-agent header set.
// This default will be added starting with builds with the SDKs for OS X 10.11 and iOS 9.
//
// To use the configuration's default user agent, set this property to nil.
@property(atomic, copy, GTM_NULLABLE) NSString *userAgent;
// The authorizer to attach to the created fetchers. If a specific fetcher should
// not authorize its requests, the fetcher's authorizer property may be set to nil
// before the fetch begins.
@property(atomic, strong, GTM_NULLABLE) id<GTMFetcherAuthorizationProtocol> authorizer;
// Delegate queue used by the session when calling back to the fetcher. The default
// is the main queue. Changing this does not affect the queue used to call back to the
// application; that is specified by the callbackQueue property above.
@property(atomic, strong, GTM_NULL_RESETTABLE) NSOperationQueue *sessionDelegateQueue;
// When enabled, indicates the same session should be used by subsequent fetchers.
//
// This is enabled by default.
@property(atomic, assign) BOOL reuseSession;
// Sets the delay until an unused session is invalidated.
// The default interval is 60 seconds.
//
// If the interval is set to 0, then any reused session is not invalidated except by
// explicitly invoking -resetSession. Be aware that setting the interval to 0 thus
// causes the session's delegate to be retained until the session is explicitly reset.
@property(atomic, assign) NSTimeInterval unusedSessionTimeout;
// If shouldReuseSession is enabled, this will force creation of a new session when future
// fetchers begin.
- (void)resetSession;
// Create a fetcher
//
// These methods will return a fetcher. If successfully created, the connection
// will hold a strong reference to it for the life of the connection as well.
// So the caller doesn't have to hold onto the fetcher explicitly unless they
// want to be able to monitor or cancel it.
- (GTMSessionFetcher *)fetcherWithRequest:(NSURLRequest *)request;
- (GTMSessionFetcher *)fetcherWithURL:(NSURL *)requestURL;
- (GTMSessionFetcher *)fetcherWithURLString:(NSString *)requestURLString;
// Common method for fetcher creation.
//
// -fetcherWithRequest:fetcherClass: may be overridden to customize creation of
// fetchers. This is the ONLY method in the GTMSessionFetcher library intended to
// be overridden.
- (id)fetcherWithRequest:(NSURLRequest *)request
fetcherClass:(Class)fetcherClass;
- (BOOL)isDelayingFetcher:(GTMSessionFetcher *)fetcher;
- (NSUInteger)numberOfFetchers; // running + delayed fetchers
- (NSUInteger)numberOfRunningFetchers;
- (NSUInteger)numberOfDelayedFetchers;
// Return a list of all running or delayed fetchers. This includes fetchers created
// by the service which have been started and have not yet stopped.
//
// Returns an array of fetcher objects, or nil if none.
- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchers;
// Search for running or delayed fetchers with the specified URL.
//
// Returns an array of fetcher objects found, or nil if none found.
- (GTM_NULLABLE GTM_NSArrayOf(GTMSessionFetcher *) *)issuedFetchersWithRequestURL:(NSURL *)requestURL;
- (void)stopAllFetchers;
// Methods for use by the fetcher class only.
- (GTM_NULLABLE NSURLSession *)session;
- (GTM_NULLABLE NSURLSession *)sessionForFetcherCreation;
- (GTM_NULLABLE id<NSURLSessionDelegate>)sessionDelegate;
- (GTM_NULLABLE NSDate *)stoppedAllFetchersDate;
// The testBlock can inspect its fetcher parameter's request property to
// determine which fetcher is being faked.
@property(atomic, copy, GTM_NULLABLE) GTMSessionFetcherTestBlock testBlock;
@end
@interface GTMSessionFetcherService (TestingSupport)
// Convenience methods to create a fetcher service for testing.
//
// Fetchers generated by this mock fetcher service will not perform any
// network operation, but will invoke callbacks and provide the supplied data
// or error to the completion handler.
//
// You can make more customized mocks by setting the test block property of the service
// or fetcher; the test block can inspect the fetcher's request or other properties.
//
// See the description of the testBlock property below.
+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil
fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil;
+ (instancetype)mockFetcherServiceWithFakedData:(GTM_NULLABLE NSData *)fakedDataOrNil
fakedResponse:(NSHTTPURLResponse *)fakedResponse
fakedError:(GTM_NULLABLE NSError *)fakedErrorOrNil;
// Spin the run loop and discard events (or, if not on the main thread, just sleep the thread)
// until all running and delayed fetchers have completed.
//
// This is only for use in testing or in tools without a user interface.
//
// Synchronous fetches should never be done by shipping apps; they are
// sufficient reason for rejection from the app store.
//
// Returns NO if timed out.
- (BOOL)waitForCompletionOfAllFetchersWithTimeout:(NSTimeInterval)timeoutInSeconds;
@end
@interface GTMSessionFetcherService (BackwardsCompatibilityOnly)
// Clients using GTMSessionFetcher should set the cookie storage explicitly themselves.
// This method is just for compatibility with the old fetcher.
@property(atomic, assign) NSInteger cookieStorageMethod;
@end
GTM_ASSUME_NONNULL_END
/* Copyright 2014 Google Inc. All rights reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// GTMSessionUploadFetcher implements Google's resumable upload protocol.
//
// This subclass of GTMSessionFetcher simulates the series of fetches
// needed for chunked upload as a single fetch operation.
//
// Protocol document: TBD
//
// To the client, the only fetcher that exists is this class; the subsidiary
// fetchers needed for uploading chunks are not visible (though the most recent
// chunk fetcher may be accessed via the -activeFetcher or -chunkFetcher methods, and
// -responseHeaders and -statusCode reflect results from the most recent chunk
// fetcher.)
//
// Chunk fetchers are discarded as soon as they have completed.
//
// The protocol also allows for a cancellation notification request to be sent to the
// server to allow discarding of the currently uploaded data and this will be sent
// automatically upon calling stopFetching if the upload has already started.
//
// Note: Unlike the fetcher superclass, the methods of GTMSessionUploadFetcher should
// only be used from the main thread until further work is done to make this subclass
// thread-safe.
#import "GTMSessionFetcher.h"
#import "GTMSessionFetcherService.h"
GTM_ASSUME_NONNULL_BEGIN
// The value to use for file size parameters when the file size is not yet known.
extern int64_t const kGTMSessionUploadFetcherUnknownFileSize;
// Unless an application knows it needs a smaller chunk size, it should use the standard
// chunk size, which sends the entire file as a single chunk to minimize upload overhead.
// Setting an explicit chunk size that comfortably fits in memory is advisable for large
// uploads.
extern int64_t const kGTMSessionUploadFetcherStandardChunkSize;
// When uploading requires data buffer allocations (such as uploading from an NSData or
// an NSFileHandle) this is the maximum buffer size that will be created by the fetcher.
extern int64_t const kGTMSessionUploadFetcherMaximumDemandBufferSize;
// Notification that the upload location URL was provided by the server.
extern NSString *const kGTMSessionFetcherUploadLocationObtainedNotification;
// Block to provide data during uploads.
//
// Response data may be allocated with dataWithBytesNoCopy:length:freeWhenDone: for efficiency,
// and released after the response block returns.
//
// If the length of the file being uploaded is unknown or already set, send
// kGTMSessionUploadFetcherUnknownFileSize for |fullUploadLength|. Otherwise, set |fullUploadLength|
// to its proper value.
//
// Pass nil as the data (and optionally an NSError) for a failure.
typedef void (^GTMSessionUploadFetcherDataProviderResponse)(NSData * GTM_NULLABLE_TYPE data,
int64_t fullUploadLength,
NSError * GTM_NULLABLE_TYPE error);
// Do not call the response with an NSData object with less data than the requested length unless
// you are passing the fullUploadLength to the fetcher for the first time and it is the last chunk
// of data in the file being uploaded.
typedef void (^GTMSessionUploadFetcherDataProvider)(int64_t offset, int64_t length,
GTMSessionUploadFetcherDataProviderResponse response);
// Block to be notified about the final status of the cancellation request started in stopFetching.
//
// |fetcher| will be the cancel request that was sent to the server, or nil if stopFetching is not
// going to send a cancel request. If |fetcher| is provided, the other parameters correspond to the
// completion handler of the cancellation request fetcher.
typedef void (^GTMSessionUploadFetcherCancellationHandler)(
GTMSessionFetcher * GTM_NULLABLE_TYPE fetcher,
NSData * GTM_NULLABLE_TYPE data,
NSError * GTM_NULLABLE_TYPE error);
@interface GTMSessionUploadFetcher : GTMSessionFetcher
// Create an upload fetcher specifying either the request or the resume location URL,
// then set an upload data source using one of these:
//
// setUploadFileURL:
// setUploadDataLength:provider:
// setUploadFileHandle:
// setUploadData:
+ (instancetype)uploadFetcherWithRequest:(NSURLRequest *)request
uploadMIMEType:(NSString *)uploadMIMEType
chunkSize:(int64_t)chunkSize
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
// Allows cellular access.
+ (instancetype)uploadFetcherWithLocation:(NSURL * GTM_NULLABLE_TYPE)uploadLocationURL
uploadMIMEType:(NSString *)uploadMIMEType
chunkSize:(int64_t)chunkSize
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
+ (instancetype)uploadFetcherWithLocation:(NSURL *GTM_NULLABLE_TYPE)uploadLocationURL
uploadMIMEType:(NSString *)uploadMIMEType
chunkSize:(int64_t)chunkSize
allowsCellularAccess:(BOOL)allowsCellularAccess
fetcherService:(GTM_NULLABLE GTMSessionFetcherService *)fetcherServiceOrNil;
// Allows dataProviders for files of unknown length. Pass kGTMSessionUploadFetcherUnknownFileSize as
// |fullLength| if the length is unknown.
- (void)setUploadDataLength:(int64_t)fullLength
provider:(GTM_NULLABLE GTMSessionUploadFetcherDataProvider)block;
+ (NSArray *)uploadFetchersForBackgroundSessions;
+ (GTM_NULLABLE instancetype)uploadFetcherForSessionIdentifier:(NSString *)sessionIdentifier;
- (void)pauseFetching;
- (void)resumeFetching;
- (BOOL)isPaused;
@property(atomic, strong, GTM_NULLABLE) NSURL *uploadLocationURL;
@property(atomic, strong, GTM_NULLABLE) NSData *uploadData;
@property(atomic, strong, GTM_NULLABLE) NSURL *uploadFileURL;
@property(atomic, strong, GTM_NULLABLE) NSFileHandle *uploadFileHandle;
@property(atomic, copy, readonly, GTM_NULLABLE) GTMSessionUploadFetcherDataProvider uploadDataProvider;
@property(atomic, copy) NSString *uploadMIMEType;
@property(atomic, readonly, assign) int64_t chunkSize;
@property(atomic, readonly, assign) int64_t currentOffset;
// Reflects the original NSURLRequest's @c allowCellularAccess property.
@property(atomic, readonly, assign) BOOL allowsCellularAccess;
// The fetcher for the current data chunk, if any
@property(atomic, strong, GTM_NULLABLE) GTMSessionFetcher *chunkFetcher;
// The active fetcher is the current chunk fetcher, or the upload fetcher itself
// if no chunk fetcher has yet been created.
@property(atomic, readonly) GTMSessionFetcher *activeFetcher;
// The last request made by an active fetcher. Useful for testing.
@property(atomic, readonly, GTM_NULLABLE) NSURLRequest *lastChunkRequest;
// The status code from the most recently-completed fetch.
@property(atomic, assign) NSInteger statusCode;
// Invoked as part of the stop fetching process. Invoked immediately if there is no upload in
// progress, otherwise invoked with the results of the attempt to notify the server that the
// upload will not continue.
//
// Unlike other callbacks, since this is related specifically to the stopFetching flow it is not
// cleared by stopFetching. It will instead clear itself after it is invoked or if the completion
// has occured before stopFetching is called.
@property(atomic, copy, GTM_NULLABLE) GTMSessionUploadFetcherCancellationHandler
cancellationHandler;
// Exposed for testing only.
@property(atomic, readonly, GTM_NULLABLE) dispatch_queue_t delegateCallbackQueue;
@property(atomic, readonly, GTM_NULLABLE) GTMSessionFetcherCompletionHandler delegateCompletionHandler;
@end
@interface GTMSessionFetcher (GTMSessionUploadFetcherMethods)
@property(readonly, GTM_NULLABLE) GTMSessionUploadFetcher *parentUploadFetcher;
@end
GTM_ASSUME_NONNULL_END
# Google APIs Client Library for Objective-C for REST #
**Project site** <https://github.com/google/google-api-objectivec-client-for-rest><br>
**Discussion group** <http://groups.google.com/group/google-api-objectivec-client>
[![Build Status](https://travis-ci.org/google/google-api-objectivec-client-for-rest.svg?branch=master)](https://travis-ci.org/google/google-api-objectivec-client-for-rest)
Written by Google, this library is a flexible and efficient Objective-C
framework for accessing JSON APIs.
This is the recommended library for accessing JSON-based Google APIs for iOS and
Mac OS X applications. The library is compatible with applications built for
iOS 7 and later, and Mac OS X 10.9 and later.
**To get started** with Google APIs and the Objective-C client library, Read the
[wiki](https://github.com/google/google-api-objectivec-client-for-rest/wiki).
See
[BuildingTheLibrary](https://github.com/google/google-api-objectivec-client-for-rest/wiki/BuildingTheLibrary)
for how to add the library to a Mac or iPhone application project, it covers
directly adding sources or using CocoaPods. Study the
[example applications](https://github.com/google/google-api-objectivec-client-for-rest/tree/master/Examples).
Generated interfaces for Google APIs are in the
[GeneratedServices folder](https://github.com/google/google-api-objectivec-client-for-rest/tree/master/Source/GeneratedServices).
In addition to the pre generated classes included with the library, you can
generate your own source for other services that have a
[discovery document](https://developers.google.com/discovery/v1/reference/apis#resource-representations)
by using the
[ServiceGenerator](https://github.com/google/google-api-objectivec-client-for-rest/wiki/ServiceGenerator).
**If you have a problem** or want a new feature to be included in the library,
please join the
[discussion group](http://groups.google.com/group/google-api-objectivec-client).
Be sure to include
[http logs](https://github.com/google/google-api-objectivec-client-for-rest/wiki#logging-http-server-traffic)
for requests and responses when posting questions. Bugs may also be submitted
on the [issues list](https://github.com/google/google-api-objectivec-client-for-rest/issues).
**Externally-included projects**: The library is built on top of code from the separate
project [GTM Session Fetcher](https://github.com/google/gtm-session-fetcher). To work
with some remote services, it also needs
[Authentication/Authorization](https://github.com/google/google-api-objectivec-client-for-rest/wiki#authentication-and-authorization).
**Google Data APIs**: The much older library for XML-based APIs is
[still available](https://github.com/google/gdata-objectivec-client).
Other useful classes for Mac and iOS developers are available in the
[Google Toolbox for Mac](https://github.com/google/google-toolbox-for-mac).
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// GTLRDefines.h
//
// Ensure Apple's conditionals we depend on are defined.
#import <TargetConditionals.h>
#import <AvailabilityMacros.h>
// These can be redefined via a prefix if you are prefixing symbols to prefix
// the names used in strings. Something like:
// #define _HELPER(x) "MyPrefix" #x
// #define GTLR_CLASSNAME_STR(x) @_HELPER(x)
// #define GTLR_CLASSNAME_CSTR(x) _HELPER(x)
#ifndef GTLR_CLASSNAME_STR
#define _GTLR_CLASSNAME_HELPER(x) #x
#define GTLR_CLASSNAME_STR(x) @_GTLR_CLASSNAME_HELPER(x)
#define GTLR_CLASSNAME_CSTR(x) _GTLR_CLASSNAME_HELPER(x)
#endif
// Provide a common definition for externing constants/functions
#if defined(__cplusplus)
#define GTLR_EXTERN extern "C"
#else
#define GTLR_EXTERN extern
#endif
//
// GTLR_ASSERT defaults to bridging to NSAssert. This macro exists just in case
// it needs to be remapped.
// GTLR_DEBUG_ASSERT is similar, but compiles in only for debug builds
//
#ifndef GTLR_ASSERT
// NSCAssert to avoid capturing self if used in a block.
#define GTLR_ASSERT(condition, ...) NSCAssert(condition, __VA_ARGS__)
#endif // GTLR_ASSERT
#ifndef GTLR_DEBUG_ASSERT
#if DEBUG && !defined(NS_BLOCK_ASSERTIONS)
#define GTLR_DEBUG_ASSERT(condition, ...) GTLR_ASSERT(condition, __VA_ARGS__)
#elif DEBUG
// In DEBUG builds with assertions blocked, log to avoid unused variable warnings.
#define GTLR_DEBUG_ASSERT(condition, ...) if (!(condition)) { NSLog(__VA_ARGS__); }
#else
#define GTLR_DEBUG_ASSERT(condition, ...) do { } while (0)
#endif
#endif
#ifndef GTLR_DEBUG_LOG
#if DEBUG
#define GTLR_DEBUG_LOG(...) NSLog(__VA_ARGS__)
#else
#define GTLR_DEBUG_LOG(...) do { } while (0)
#endif
#endif
#ifndef GTLR_DEBUG_ASSERT_CURRENT_QUEUE
#define GTLR_ASSERT_CURRENT_QUEUE_DEBUG(targetQueue) \
GTLR_DEBUG_ASSERT(0 == strcmp(GTLR_QUEUE_NAME(targetQueue), \
GTLR_QUEUE_NAME(DISPATCH_CURRENT_QUEUE_LABEL)), \
@"Current queue is %s (expected %s)", \
GTLR_QUEUE_NAME(DISPATCH_CURRENT_QUEUE_LABEL), \
GTLR_QUEUE_NAME(targetQueue))
#define GTLR_QUEUE_NAME(queue) \
(strlen(dispatch_queue_get_label(queue)) > 0 ? dispatch_queue_get_label(queue) : "unnamed")
#endif // GTLR_ASSERT_CURRENT_QUEUE_DEBUG
// Sanity check the min versions.
#if (defined(TARGET_OS_TV) && TARGET_OS_TV) || (defined(TARGET_OS_WATCH) && TARGET_OS_WATCH)
// No min checks for these two.
#elif TARGET_OS_IPHONE
#if !defined(__IPHONE_9_0) || (__IPHONE_OS_VERSION_MAX_ALLOWED < __IPHONE_9_0)
#error "This project expects to be compiled with the iOS 9.0 SDK (or later)."
#endif
#if __IPHONE_OS_VERSION_MIN_REQUIRED < __IPHONE_7_0
#error "The minimum supported iOS version is 7.0."
#endif
#elif TARGET_OS_MAC
#if !defined(MAC_OS_X_VERSION_10_10) || (MAC_OS_X_VERSION_MAX_ALLOWED < MAC_OS_X_VERSION_10_10)
#error "This project expects to be compiled with the OS X 10.10 SDK (or later)."
#endif
#if MAC_OS_X_VERSION_MIN_REQUIRED < MAC_OS_X_VERSION_10_9
#error "The minimum supported OS X version is 10.9."
#endif
#else
#error "Unknown target platform."
#endif
// Version marker used to validate the generated sources against the library
// version. The will be changed any time the library makes a change that means
// past sources need to be regenerated.
#define GTLR_RUNTIME_VERSION 3000
// NOTE: This file was generated by the ServiceGenerator.
// ----------------------------------------------------------------------------
// API:
// Drive API (drive/v3)
// Description:
// Manages files in Drive including uploading, downloading, searching,
// detecting changes, and updating sharing permissions.
// Documentation:
// https://developers.google.com/drive/
#import "GTLRDriveObjects.h"
#import "GTLRDriveQuery.h"
#import "GTLRDriveService.h"
// NOTE: This file was generated by the ServiceGenerator.
// ----------------------------------------------------------------------------
// API:
// Drive API (drive/v3)
// Description:
// Manages files in Drive including uploading, downloading, searching,
// detecting changes, and updating sharing permissions.
// Documentation:
// https://developers.google.com/drive/
#if GTLR_BUILT_AS_FRAMEWORK
#import "GTLR/GTLRService.h"
#else
#import "GTLRService.h"
#endif
#if GTLR_RUNTIME_VERSION != 3000
#error This file was generated by a different version of ServiceGenerator which is incompatible with this GTLR library source.
#endif
// Generated comments include content from the discovery document; avoid them
// causing warnings since clang's checks are some what arbitrary.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdocumentation"
NS_ASSUME_NONNULL_BEGIN
// ----------------------------------------------------------------------------
// Authorization scopes
/**
* Authorization scope: See, edit, create, and delete all of your Google Drive
* files
*
* Value "https://www.googleapis.com/auth/drive"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDrive;
/**
* Authorization scope: View and manage its own configuration data in your
* Google Drive
*
* Value "https://www.googleapis.com/auth/drive.appdata"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDriveAppdata;
/**
* Authorization scope: View and manage Google Drive files and folders that you
* have opened or created with this app
*
* Value "https://www.googleapis.com/auth/drive.file"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDriveFile;
/**
* Authorization scope: View and manage metadata of files in your Google Drive
*
* Value "https://www.googleapis.com/auth/drive.metadata"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDriveMetadata;
/**
* Authorization scope: View metadata for files in your Google Drive
*
* Value "https://www.googleapis.com/auth/drive.metadata.readonly"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDriveMetadataReadonly;
/**
* Authorization scope: View the photos, videos and albums in your Google
* Photos
*
* Value "https://www.googleapis.com/auth/drive.photos.readonly"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDrivePhotosReadonly;
/**
* Authorization scope: See and download all your Google Drive files
*
* Value "https://www.googleapis.com/auth/drive.readonly"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDriveReadonly;
/**
* Authorization scope: Modify your Google Apps Script scripts' behavior
*
* Value "https://www.googleapis.com/auth/drive.scripts"
*/
GTLR_EXTERN NSString * const kGTLRAuthScopeDriveScripts;
// ----------------------------------------------------------------------------
// GTLRDriveService
//
/**
* Service for executing Drive API queries.
*
* Manages files in Drive including uploading, downloading, searching,
* detecting changes, and updating sharing permissions.
*/
@interface GTLRDriveService : GTLRService
// No new methods
// Clients should create a standard query with any of the class methods in
// GTLRDriveQuery.h. The query can the be sent with GTLRService's execute
// methods,
//
// - (GTLRServiceTicket *)executeQuery:(GTLRQuery *)query
// completionHandler:(void (^)(GTLRServiceTicket *ticket,
// id object, NSError *error))handler;
// or
// - (GTLRServiceTicket *)executeQuery:(GTLRQuery *)query
// delegate:(id)delegate
// didFinishSelector:(SEL)finishedSelector;
//
// where finishedSelector has a signature of:
//
// - (void)serviceTicket:(GTLRServiceTicket *)ticket
// finishedWithObject:(id)object
// error:(NSError *)error;
//
// The object passed to the completion handler or delegate method
// is a subclass of GTLRObject, determined by the query method executed.
@end
NS_ASSUME_NONNULL_END
#pragma clang diagnostic pop
// NOTE: This file was generated by the ServiceGenerator.
// ----------------------------------------------------------------------------
// API:
// Drive API (drive/v3)
// Description:
// Manages files in Drive including uploading, downloading, searching,
// detecting changes, and updating sharing permissions.
// Documentation:
// https://developers.google.com/drive/
#import "GTLRDrive.h"
// ----------------------------------------------------------------------------
// Authorization scopes
NSString * const kGTLRAuthScopeDrive = @"https://www.googleapis.com/auth/drive";
NSString * const kGTLRAuthScopeDriveAppdata = @"https://www.googleapis.com/auth/drive.appdata";
NSString * const kGTLRAuthScopeDriveFile = @"https://www.googleapis.com/auth/drive.file";
NSString * const kGTLRAuthScopeDriveMetadata = @"https://www.googleapis.com/auth/drive.metadata";
NSString * const kGTLRAuthScopeDriveMetadataReadonly = @"https://www.googleapis.com/auth/drive.metadata.readonly";
NSString * const kGTLRAuthScopeDrivePhotosReadonly = @"https://www.googleapis.com/auth/drive.photos.readonly";
NSString * const kGTLRAuthScopeDriveReadonly = @"https://www.googleapis.com/auth/drive.readonly";
NSString * const kGTLRAuthScopeDriveScripts = @"https://www.googleapis.com/auth/drive.scripts";
// ----------------------------------------------------------------------------
// GTLRDriveService
//
@implementation GTLRDriveService
- (instancetype)init {
self = [super init];
if (self) {
// From discovery.
self.rootURLString = @"https://www.googleapis.com/";
self.servicePath = @"drive/v3/";
self.resumableUploadPath = @"resumable/upload/";
self.simpleUploadPath = @"upload/";
self.batchPath = @"batch/drive/v3";
self.prettyPrintQueryParameterNames = @[ @"prettyPrint" ];
}
return self;
}
+ (NSDictionary<NSString *, Class> *)kindStringToClassMap {
return @{
@"api#channel" : [GTLRDrive_Channel class],
@"drive#about" : [GTLRDrive_About class],
@"drive#change" : [GTLRDrive_Change class],
@"drive#changeList" : [GTLRDrive_ChangeList class],
@"drive#comment" : [GTLRDrive_Comment class],
@"drive#commentList" : [GTLRDrive_CommentList class],
@"drive#drive" : [GTLRDrive_Drive class],
@"drive#driveList" : [GTLRDrive_DriveList class],
@"drive#file" : [GTLRDrive_File class],
@"drive#fileList" : [GTLRDrive_FileList class],
@"drive#generatedIds" : [GTLRDrive_GeneratedIds class],
@"drive#permission" : [GTLRDrive_Permission class],
@"drive#permissionList" : [GTLRDrive_PermissionList class],
@"drive#reply" : [GTLRDrive_Reply class],
@"drive#replyList" : [GTLRDrive_ReplyList class],
@"drive#revision" : [GTLRDrive_Revision class],
@"drive#revisionList" : [GTLRDrive_RevisionList class],
@"drive#startPageToken" : [GTLRDrive_StartPageToken class],
@"drive#teamDrive" : [GTLRDrive_TeamDrive class],
@"drive#teamDriveList" : [GTLRDrive_TeamDriveList class],
@"drive#user" : [GTLRDrive_User class],
};
}
@end
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Batch query documentation:
// https://github.com/google/google-api-objectivec-client-for-rest/wiki#batch-operations
#import "GTLRQuery.h"
NS_ASSUME_NONNULL_BEGIN
@interface GTLRBatchQuery : NSObject <GTLRQueryProtocol>
/**
* Queries included in this batch. Each query should have a unique @c requestID.
*/
@property(atomic, copy, nullable) NSArray<GTLRQuery *> *queries;
/**
* Flag indicating if query execution should skip authorization. Defaults to NO.
*/
@property(atomic, assign) BOOL shouldSkipAuthorization;
/**
* Any additional HTTP headers for this batch.
*
* These headers override the same keys from the service object's
* @c additionalHTTPHeaders.
*/
@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalHTTPHeaders;
/**
* Any additional URL query parameters to add to the batch query.
*
* These query parameters override the same keys from the service object's
* @c additionalURLQueryParameters
*/
@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalURLQueryParameters;
/**
* The batch request multipart boundary, once determined.
*/
@property(atomic, copy, nullable) NSString *boundary;
/**
* The brief string to identify this query in @c GTMSessionFetcher http logs.
*
* The default logging name for batch requests includes the API method names.
*/
@property(atomic, copy, nullable) NSString *loggingName;
/**
* Constructor for a batch query, for use with @c addQuery:
*/
+ (instancetype)batchQuery;
/**
* Constructor for a batch query, from an array of @c GTLRQuery objects.
*/
+ (instancetype)batchQueryWithQueries:(NSArray<GTLRQuery *> *)array;
/**
* Add a single @c GTLRQuery to the batch.
*/
- (void)addQuery:(GTLRQuery *)query;
/**
* Search the batch for a query with the specified ID.
*/
- (nullable GTLRQuery *)queryForRequestID:(NSString *)requestID;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !__has_feature(objc_arc)
#error "This file needs to be compiled with ARC enabled."
#endif
#import "GTLRBatchQuery.h"
#import "GTLRService.h"
#if DEBUG
static void DebugAssertValidBatchQueryItem(GTLRQuery *query) {
GTLR_DEBUG_ASSERT([query isKindOfClass:[GTLRQuery class]],
@"unexpected query class: %@", [query class]);
GTLR_DEBUG_ASSERT(query.uploadParameters == nil,
@"batch may not contain upload: %@", query);
GTLR_DEBUG_ASSERT(!query.hasExecutionParameters,
@"queries added to a batch may not contain executionParameters: %@", query);
GTLR_DEBUG_ASSERT(!query.queryInvalid,
@"batch may not contain query already executed: %@", query);
}
#else
static void DebugAssertValidBatchQueryItem(GTLRQuery *query) { }
#endif
@implementation GTLRBatchQuery {
NSMutableArray<GTLRQuery *> *_queries;
NSMutableDictionary *_requestIDMap;
GTLRServiceExecutionParameters *_executionParameters;
}
@synthesize shouldSkipAuthorization = _shouldSkipAuthorization,
additionalHTTPHeaders = _additionalHTTPHeaders,
additionalURLQueryParameters = _additionalURLQueryParameters,
boundary = _boundary,
loggingName = _loggingName;
+ (instancetype)batchQuery {
GTLRBatchQuery *obj = [[self alloc] init];
return obj;
}
+ (instancetype)batchQueryWithQueries:(NSArray<GTLRQuery *> *)queries {
GTLRBatchQuery *obj = [self batchQuery];
obj.queries = queries;
#if DEBUG
for (GTLRQuery *query in queries) {
DebugAssertValidBatchQueryItem(query);
}
#endif
return obj;
}
- (id)copyWithZone:(NSZone *)zone {
// Deep copy the list of queries
GTLRBatchQuery *newBatch = [[[self class] allocWithZone:zone] init];
if (_queries) {
newBatch.queries = [[NSArray alloc] initWithArray:_queries
copyItems:YES];
}
// Using the executionParameters ivar avoids creating the object.
newBatch.executionParameters = _executionParameters;
// Copied in the same order as synthesized above.
newBatch.shouldSkipAuthorization = _shouldSkipAuthorization;
newBatch.additionalHTTPHeaders = _additionalHTTPHeaders;
newBatch.additionalURLQueryParameters = _additionalURLQueryParameters;
newBatch.boundary = _boundary;
newBatch.loggingName = _loggingName;
// No need to copy _requestIDMap as it's created on demand.
return newBatch;
}
- (NSString *)description {
NSArray *queries = self.queries;
NSArray *loggingNames = [queries valueForKey:@"loggingName"];
NSMutableSet *dedupedNames = [NSMutableSet setWithArray:loggingNames]; // de-dupe
[dedupedNames removeObject:[NSNull null]]; // In case any didn't have a loggingName.
NSString *namesStr = [[dedupedNames allObjects] componentsJoinedByString:@","];
return [NSString stringWithFormat:@"%@ %p (queries:%lu - %@)",
[self class], self, (unsigned long)queries.count, namesStr];
}
#pragma mark -
- (BOOL)isBatchQuery {
return YES;
}
- (GTLRUploadParameters *)uploadParameters {
// File upload is not supported for batches
return nil;
}
- (void)invalidateQuery {
NSArray *queries = self.queries;
[queries makeObjectsPerformSelector:@selector(invalidateQuery)];
_executionParameters = nil;
}
- (GTLRQuery *)queryForRequestID:(NSString *)requestID {
GTLRQuery *result = [_requestIDMap objectForKey:requestID];
if (result) return result;
// We've not before tried to look up a query, or the map is stale
_requestIDMap = [[NSMutableDictionary alloc] init];
for (GTLRQuery *query in _queries) {
[_requestIDMap setObject:query forKey:query.requestID];
}
result = [_requestIDMap objectForKey:requestID];
return result;
}
#pragma mark -
- (void)setQueries:(NSArray<GTLRQuery *> *)array {
#if DEBUG
for (GTLRQuery *query in array) {
DebugAssertValidBatchQueryItem(query);
}
#endif
_queries = [array mutableCopy];
}
- (NSArray<GTLRQuery *> *)queries {
return _queries;
}
- (void)addQuery:(GTLRQuery *)query {
DebugAssertValidBatchQueryItem(query);
if (_queries == nil) {
_queries = [[NSMutableArray alloc] init];
}
[_queries addObject:query];
}
- (GTLRServiceExecutionParameters *)executionParameters {
@synchronized(self) {
if (!_executionParameters) {
_executionParameters = [[GTLRServiceExecutionParameters alloc] init];
}
}
return _executionParameters;
}
- (void)setExecutionParameters:(GTLRServiceExecutionParameters *)executionParameters {
@synchronized(self) {
_executionParameters = executionParameters;
}
}
- (BOOL)hasExecutionParameters {
return _executionParameters.hasParameters;
}
@end
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GTLRObject.h"
NS_ASSUME_NONNULL_BEGIN
@class GTLRErrorObject;
/**
* A batch result includes a dictionary of successes, a dictionary of failures, and a dictionary of
* HTTP response headers.
*
* Dictionary keys are request ID strings; dictionary values are @c GTLRObject for
* successes, @c GTLRErrorObject for failures, @c NSDictionary for responseHeaders.
*
* For successes with no returned object (such as from delete operations),
* the object for the dictionary entry is @c NSNull.
*
* The original query for each result is available from the service ticket, as shown in
* the code snippet here.
*
* When the queries in the batch are unrelated, adding a @c completionBlock to each of
* the queries may be a simpler way to handle the batch results.
*
* @code
* NSDictionary *successes = batchResults.successes;
* for (NSString *requestID in successes) {
* GTLRObject *obj = successes[requestID];
* GTLRQuery *query = [ticket queryForRequestID:requestID];
* NSLog(@"Query %@ returned object %@", query, obj);
* }
*
* NSDictionary *failures = batchResults.failures;
* for (NSString *requestID in failures) {
* GTLRErrorObject *errorObj = failures[requestID];
* GTLRQuery *query = [ticket queryForRequestID:requestID];
* NSLog(@"Query %@ failed with error %@", query, errorObj);
* }
* @endcode
*/
@interface GTLRBatchResult : GTLRObject
/**
* Object results of successful queries in the batch, keyed by request ID.
*
* Queries which do not return an object when successful have a @c NSNull value.
*/
@property(atomic, strong, nullable) NSDictionary<NSString *, __kindof GTLRObject *> *successes;
/**
* Object results of unsuccessful queries in the batch, keyed by request ID.
*/
@property(atomic, strong, nullable) NSDictionary<NSString *, GTLRErrorObject *> *failures;
/**
* Any HTTP response headers that were returned for a query request. Headers are optional therefore
* not all queries will have them. Query request with response headers are stored in a
* dictionary and keyed by request ID.
*/
@property(atomic, strong, nullable)
NSDictionary<NSString *, NSDictionary *> *responseHeaders;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !__has_feature(objc_arc)
#error "This file needs to be compiled with ARC enabled."
#endif
#import "GTLRBatchResult.h"
#import "GTLRErrorObject.h"
#import "GTLRUtilities.h"
static NSString *const kGTLRBatchResultSuccessesKeys = @"successesKeys";
static NSString *const kGTLRBatchResultSuccessKeyPrefix = @"Success-";
static NSString *const kGTLRBatchResultFailuresKeys = @"failuresKeys";
static NSString *const kGTLRBatchResultFailurKeyPrefix = @"Failure-";
static NSString *const kGTLRBatchResultResponseHeaders = @"responseHeaders";
@implementation GTLRBatchResult
@synthesize successes = _successes,
failures = _failures,
responseHeaders = _responseHeaders;
// Since this class doesn't use the json property, provide the basic NSObject
// methods needed to ensure proper behaviors.
- (id)copyWithZone:(NSZone *)zone {
GTLRBatchResult* newObject = [super copyWithZone:zone];
newObject.successes = [self.successes copyWithZone:zone];
newObject.failures = [self.failures copyWithZone:zone];
newObject.responseHeaders = [self.responseHeaders copyWithZone:zone];
return newObject;
}
- (NSUInteger)hash {
NSUInteger result = [super hash];
result += result * 13 + [self.successes hash];
result += result * 13 + [self.failures hash];
result += result * 13 + [self.responseHeaders hash];
return result;
}
- (BOOL)isEqual:(id)object {
if (self == object) return YES;
if (![super isEqual:object]) {
return NO;
}
if (![object isKindOfClass:[GTLRBatchResult class]]) {
return NO;
}
GTLRBatchResult *other = (GTLRBatchResult *)object;
if (!GTLR_AreEqualOrBothNil(self.successes, other.successes)) {
return NO;
}
if (!GTLR_AreEqualOrBothNil(self.failures, other.failures)) {
return NO;
}
return GTLR_AreEqualOrBothNil(self.responseHeaders, other.responseHeaders);
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %p (successes:%lu failures:%lu responseHeaders:%lu)",
[self class], self,
(unsigned long)self.successes.count,
(unsigned long)self.failures.count,
(unsigned long)self.responseHeaders.count];
}
// This class is a subclass of GTLRObject, which declares NSSecureCoding
// conformance. Since this class does't really use the json property, provide
// a custom implementation to maintain the contract.
//
// For success/failures, one could do:
// [encoder encodeObject:self.successes forKey:kGTLRBatchResultSuccesses];
// [encoder encodeObject:self.failures forKey:kGTLRBatchResultFailuresKeys];
// and then use -decodeObjectOfClasses:forKey:, but nothing actually checks the
// structure of the dictionary, so instead the dicts are blown out to provide
// better validation by the encoder/decoder.
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
NSArray<NSString *> *keys =
[decoder decodeObjectOfClass:[NSArray class]
forKey:kGTLRBatchResultSuccessesKeys];
if (keys.count) {
NSMutableDictionary *dict =
[NSMutableDictionary dictionaryWithCapacity:keys.count];
for (NSString *key in keys) {
NSString *storageKey =
[kGTLRBatchResultSuccessKeyPrefix stringByAppendingString:key];
GTLRObject *obj = [decoder decodeObjectOfClass:[GTLRObject class]
forKey:storageKey];
if (obj) {
[dict setObject:obj forKey:key];
}
}
self.successes = dict;
}
keys = [decoder decodeObjectOfClass:[NSArray class]
forKey:kGTLRBatchResultFailuresKeys];
if (keys.count) {
NSMutableDictionary *dict =
[NSMutableDictionary dictionaryWithCapacity:keys.count];
for (NSString *key in keys) {
NSString *storageKey =
[kGTLRBatchResultFailurKeyPrefix stringByAppendingString:key];
GTLRObject *obj = [decoder decodeObjectOfClass:[GTLRObject class]
forKey:storageKey];
if (obj) {
[dict setObject:obj forKey:key];
}
}
self.failures = dict;
}
self.responseHeaders =
[decoder decodeObjectOfClass:[NSDictionary class]
forKey:kGTLRBatchResultResponseHeaders];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:self.successes.allKeys
forKey:kGTLRBatchResultSuccessesKeys];
[self.successes enumerateKeysAndObjectsUsingBlock:^(NSString *key,
GTLRObject * obj,
BOOL * stop) {
NSString *storageKey =
[kGTLRBatchResultSuccessKeyPrefix stringByAppendingString:key];
[encoder encodeObject:obj forKey:storageKey];
}];
[encoder encodeObject:self.failures.allKeys forKey:kGTLRBatchResultFailuresKeys];
[self.failures enumerateKeysAndObjectsUsingBlock:^(NSString *key,
GTLRObject * obj,
BOOL * stop) {
NSString *storageKey =
[kGTLRBatchResultFailurKeyPrefix stringByAppendingString:key];
[encoder encodeObject:obj forKey:storageKey];
}];
[encoder encodeObject:self.responseHeaders
forKey:kGTLRBatchResultResponseHeaders];
}
@end
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GTLRDefines.h"
NS_ASSUME_NONNULL_BEGIN
/**
* An immutable class representing a date and optionally a time in UTC.
*/
@interface GTLRDateTime : NSObject <NSCopying>
/**
* Constructor from a string representation.
*/
+ (nullable instancetype)dateTimeWithRFC3339String:(nullable NSString *)str;
/**
* Constructor from a date and time representation.
*/
+ (instancetype)dateTimeWithDate:(NSDate *)date;
/**
* Constructor from a date and time representation, along with an offset
* minutes value used when creating a RFC3339 string representation.
*
* The date value is independent of time zone; the offset affects how the
* date will be rendered as a string.
*
* The offsetMinutes may be initialized from a NSTimeZone as
* (timeZone.secondsFromGMT / 60)
*/
+ (instancetype)dateTimeWithDate:(NSDate *)date
offsetMinutes:(NSInteger)offsetMinutes;
/**
* Constructor from a date for an all-day event.
*
* Use this constructor to create a @c GTLRDateTime that is "date only".
*
* @note @c hasTime will be set to NO.
*/
+ (instancetype)dateTimeForAllDayWithDate:(NSDate *)date;
/**
* Constructor from date components.
*/
+ (instancetype)dateTimeWithDateComponents:(NSDateComponents *)date;
/**
* The represented date and time.
*
* If @c hasTime is NO, the time is set to noon GMT so the date is valid for all time zones.
*/
@property(nonatomic, readonly) NSDate *date;
/**
* The date and time as a RFC3339 string representation.
*/
@property(nonatomic, readonly) NSString *RFC3339String;
/**
* The date and time as a RFC3339 string representation.
*
* This returns the same string as @c RFC3339String.
*/
@property(nonatomic, readonly) NSString *stringValue;
/**
* The represented date and time as date components.
*/
@property(nonatomic, readonly, copy) NSDateComponents *dateComponents;
/**
* The fraction of seconds represented, 0-999.
*/
@property(nonatomic, readonly) NSInteger milliseconds;
/**
* The time offset displayed in the string representation, if any.
*
* If the offset is not nil, the date and time will be rendered as a string
* for the time zone indicated by the offset.
*
* An app may create a NSTimeZone for this with
* [NSTimeZone timeZoneForSecondsFromGMT:(offsetMinutes.integerValue * 60)]
*/
@property(nonatomic, readonly, nullable) NSNumber *offsetMinutes;
/**
* Flag indicating if the object represents date only, or date with time.
*/
@property(nonatomic, readonly) BOOL hasTime;
/**
* The calendar used by this class, Gregorian and UTC.
*/
+ (NSCalendar *)calendar;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import <Foundation/Foundation.h>
#import "GTLRDefines.h"
NS_ASSUME_NONNULL_BEGIN
/**
* An immutable class representing a string data type 'google-duration'.
* It is based off the protocol buffers definition:
* https://github.com/google/protobuf/blob/master/src/google/protobuf/duration.proto
*/
@interface GTLRDuration : NSObject <NSCopying>
/**
* Signed seconds of the span of time. Must be from -315,576,000,000
* to +315,576,000,000 inclusive.
**/
@property(nonatomic, readonly) int64_t seconds;
/**
* Signed fractions of a second at nanosecond resolution of the span
* of time. Durations less than one second are represented with a 0
* `seconds` field and a positive or negative `nanos` field. For durations
* of one second or more, a non-zero value for the `nanos` field must be
* of the same sign as the `seconds` field. Must be from -999,999,999
* to +999,999,999 inclusive.
**/
@property(nonatomic, readonly) int32_t nanos;
/**
* This duration expressed as a NSTimeInterval.
*
* @note: Not all second/nanos combinations can be represented in a
* NSTimeInterval, so this could be a lossy transform.
**/
@property(nonatomic, readonly) NSTimeInterval timeInterval;
/**
* Returns the string form used to send this data type in a JSON payload.
*/
@property(nonatomic, readonly) NSString *jsonString;
/**
* Constructor for a new duration with the given seconds and nanoseconds.
*
* Will fail if seconds/nanos differ in sign or if nanos is more than one
* second.
**/
+ (nullable instancetype)durationWithSeconds:(int64_t)seconds
nanos:(int32_t)nanos;
/**
* Constructor for a new duration from the given string form.
*
* Will return nil if jsonString is invalid.
**/
+ (nullable instancetype)durationWithJSONString:(nullable NSString *)jsonString;
/**
* Constructor for a new duration from the NSTimeInterval.
*
* @note NSTimeInterval doesn't always express things as exactly as one might
* expect, so coverting from to integer seconds & nanos can reveal this.
**/
+ (instancetype)durationWithTimeInterval:(NSTimeInterval)timeInterval;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2016 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !__has_feature(objc_arc)
#error "This file needs to be compiled with ARC enabled."
#endif
#import "GTLRDuration.h"
static const int32_t kNanosPerMillisecond = 1000000;
static const int32_t kNanosPerMicrosecond = 1000;
static const int32_t kNanosPerSecond = 1000000000;
static int32_t IntPow10(int x) {
int32_t result = 1;
for (int i = 0; i < x; ++i) {
result *= 10;
}
return result;
}
@implementation GTLRDuration
@dynamic timeInterval;
@synthesize seconds = _seconds,
nanos = _nanos,
jsonString = _jsonString;
+ (instancetype)durationWithSeconds:(int64_t)seconds nanos:(int32_t)nanos {
if (seconds < 0) {
if (nanos > 0) {
// secs was -, nanos was +
return nil;
}
} else if (seconds > 0) {
if (nanos < 0) {
// secs was +, nanos was -
return nil;
}
}
if ((nanos <= -kNanosPerSecond) || (nanos >= kNanosPerSecond)) {
// more than a seconds worth
return nil;
}
return [[self alloc] initWithSeconds:seconds nanos:nanos jsonString:NULL];
}
+ (instancetype)durationWithJSONString:(NSString *)jsonString {
// It has to end in "s", so it needs >1 character.
if (jsonString.length <= 1) {
return nil;
}
static NSCharacterSet *gNumberSet;
static dispatch_once_t onceToken;
dispatch_once(&onceToken, ^{
gNumberSet = [NSCharacterSet characterSetWithCharactersInString:@"0123456789"];
});
NSScanner* scanner = [NSScanner scannerWithString:jsonString];
// There should be no whitespace, so no skip characters.
[scanner setCharactersToBeSkipped:nil];
// Can start with a '-'.
BOOL isNeg = [scanner scanString:@"-" intoString:NULL];
int64_t seconds;
if (![scanner scanLongLong:&seconds]) {
return nil;
}
// Since the sign was manually scanned, seconds should be positive
// (i.e. no "--#" in the put).
if (seconds < 0) {
return nil;
}
// See if it has a ".[nanos]". Spec seems to say it is required, but play
// it safe and make it optional.
int32_t nanos = 0;
if ([scanner scanString:@"." intoString:NULL]) {
NSString *nanosStr;
if (![scanner scanCharactersFromSet:gNumberSet intoString:&nanosStr]) {
return nil;
}
// Ensure not too many digits (also ensure it is within range).
if (nanosStr.length > 9) {
return nil;
}
// Can use NSString's intValue since the character set was controlled.
nanos = [nanosStr intValue];
// Scale based on length.
nanos *= IntPow10(9 - (int)nanosStr.length);
}
// And must have the final 's'.
if (![scanner scanString:@"s" intoString:NULL]) {
return nil;
}
// Better be the end...
if (![scanner isAtEnd]) {
return nil;
}
if (isNeg) {
seconds = -seconds;
nanos = -nanos;
}
// Pass on the json string so it will be reflected back out as it came in
// (incase it had a different number of digits, etc).
return [[self alloc] initWithSeconds:seconds
nanos:nanos
jsonString:jsonString];
}
+ (instancetype)durationWithTimeInterval:(NSTimeInterval)timeInterval {
NSTimeInterval seconds;
NSTimeInterval nanos = modf(timeInterval, &seconds);
nanos *= (NSTimeInterval)kNanosPerSecond;
return [[self alloc] initWithSeconds:(int64_t)seconds
nanos:(int32_t)nanos
jsonString:NULL];
}
- (instancetype)init {
return [self initWithSeconds:0 nanos:0 jsonString:NULL];
}
- (instancetype)initWithSeconds:(int64_t)seconds
nanos:(int32_t)nanos
jsonString:(NSString *)jsonString {
self = [super init];
if (self) {
// Sanity asserts, the class methods should make sure this doesn't happen.
GTLR_DEBUG_ASSERT((((seconds <= 0) && (nanos <= 0)) ||
((seconds >= 0) && (nanos >= 0))),
@"Seconds and nanos must have the same sign (%lld & %d)",
seconds, nanos);
GTLR_DEBUG_ASSERT(((nanos < kNanosPerSecond) &&
(nanos > -kNanosPerSecond)),
@"Nanos is a second or more (%d)", nanos);
_seconds = seconds;
_nanos = nanos;
if (jsonString.length) {
_jsonString = [jsonString copy];
} else {
// Based off the JSON serialization code in protocol buffers
// ( https://github.com/google/protobuf/ ).
NSString *sign = @"";
if ((seconds < 0) || (nanos < 0)) {
sign = @"-";
seconds = -seconds;
nanos = -nanos;
}
int nanoDigts;
int32_t nanoDivider;
if (nanos % kNanosPerMillisecond == 0) {
nanoDigts = 3;
nanoDivider = kNanosPerMillisecond;
} else if (nanos % kNanosPerMicrosecond == 0) {
nanoDigts = 6;
nanoDivider = kNanosPerMicrosecond;
} else {
nanoDigts = 9;
nanoDivider = 1;
}
_jsonString = [NSString stringWithFormat:@"%@%lld.%0*ds",
sign, seconds, nanoDigts, (nanos / nanoDivider)];
}
}
return self;
}
- (NSTimeInterval)timeInterval {
NSTimeInterval result = self.seconds;
result += (NSTimeInterval)self.nanos / (NSTimeInterval)kNanosPerSecond;
return result;
}
- (id)copyWithZone:(NSZone *)zone {
// Object is immutable
return self;
}
- (BOOL)isEqual:(GTLRDuration *)other {
if (self == other) return YES;
if (![other isKindOfClass:[GTLRDuration class]]) return NO;
BOOL result = ((self.seconds == other.seconds) &&
(self.nanos == other.nanos));
return result;
}
- (NSUInteger)hash {
NSUInteger result = (NSUInteger)((self.seconds * 13) + self.nanos);
return result;
}
- (NSString *)description {
return [NSString stringWithFormat:@"%@ %p: {%@}",
[self class], self, self.jsonString];
}
@end
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#import "GTLRObject.h"
NS_ASSUME_NONNULL_BEGIN
@class GTLRErrorObjectErrorItem;
@class GTLRErrorObjectDetail;
/**
* This class wraps JSON responses (both V1 and V2 of Google JSON errors) and NSErrors.
*
* A GTLRErrorObject can be created using +objectWithJSON: or +objectWithFoundationError:
*/
@interface GTLRErrorObject : GTLRObject
/**
* Convenience method for creating an error object from an NSError.
*
* @param error The @c NSError to be encapsulated by the @c GTLRErrorObject
*
* @return A @c GTLRErrorObject wrapping the NSError.
*/
+ (instancetype)objectWithFoundationError:(NSError *)error;
/**
* Convenience utility for extracting the GTLRErrorObject that was used to create an NSError.
*
* @param foundationError The NSError that may have been obtained from a GTLRErrorObject.
*
* @return The GTLRErrorObject, nil if the error was not originally from a GTLRErrorObject.
*/
+ (nullable GTLRErrorObject *)underlyingObjectForError:(NSError *)foundationError;
//
// V1 & V2 properties.
//
/**
* The numeric error code.
*/
@property(nonatomic, strong, nullable) NSNumber *code;
/**
* An error message string, typically provided by the API server. This is not localized,
* and its reliability depends on the API server.
*/
@property(nonatomic, strong, nullable) NSString *message;
//
// V1 properties.
//
/**
* Underlying errors that occurred on the server.
*/
@property(nonatomic, strong, nullable) NSArray<GTLRErrorObjectErrorItem *> *errors;
//
// V2 properties
//
/**
* A status error string, defined by the API server, such as "NOT_FOUND".
*/
@property(nonatomic, strong, nullable) NSString *status;
/**
* Additional diagnostic error details provided by the API server.
*/
@property(nonatomic, strong, nullable) NSArray<GTLRErrorObjectDetail *> *details;
/**
* An NSError, either underlying the error object or manufactured from the error object's
* properties.
*/
@property(nonatomic, readonly) NSError *foundationError;
@end
/**
* Class representing the items of the "errors" array inside the Google V1 error JSON.
*
* Client applications should not rely on the property values of these items.
*/
@interface GTLRErrorObjectErrorItem : GTLRObject
@property(nonatomic, strong, nullable) NSString *domain;
@property(nonatomic, strong, nullable) NSString *reason;
@property(nonatomic, strong, nullable) NSString *message;
@property(nonatomic, strong, nullable) NSString *location;
@end
/**
* Class representing the items of the "details" array inside the Google V2 error JSON.
*
* Client applications should not rely on the property values of these items.
*/
@interface GTLRErrorObjectDetail : GTLRObject
@property(nonatomic, strong, nullable) NSString *type;
@property(nonatomic, strong, nullable) NSString *detail;
@end
NS_ASSUME_NONNULL_END
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#if !__has_feature(objc_arc)
#error "This file needs to be compiled with ARC enabled."
#endif
#import "GTLRErrorObject.h"
#import "GTLRUtilities.h"
#import "GTLRService.h"
static NSString *const kGTLRErrorObjectFoundationErrorKey = @"foundationError";
@implementation GTLRErrorObject {
NSError *_originalFoundationError;
}
// V1 & V2 properties.
@dynamic code;
@dynamic message;
// V1 properties.
@dynamic errors;
// V2 properties.
@dynamic status;
@dynamic details;
// Implemented below.
@dynamic foundationError;
+ (instancetype)objectWithFoundationError:(NSError *)error {
GTLRErrorObject *object = [self object];
object->_originalFoundationError = error;
object.code = @(error.code);
object.message = error.localizedDescription;
return object;
}
+ (NSDictionary *)arrayPropertyToClassMap {
return @{
@"errors" : [GTLRErrorObjectErrorItem class],
@"details" : [GTLRErrorObjectDetail class]
};
}
- (NSError *)foundationError {
// If there was an original foundation error, copy its userInfo as the basis for ours.
NSMutableDictionary *userInfo =
[NSMutableDictionary dictionaryWithDictionary:_originalFoundationError.userInfo];
// This structured GTLRErrorObject will be available in the error's userInfo
// dictionary.
userInfo[kGTLRStructuredErrorKey] = self;
NSError *error;
if (_originalFoundationError) {
error = [NSError errorWithDomain:_originalFoundationError.domain
code:_originalFoundationError.code
userInfo:userInfo];
} else {
NSString *reasonStr = self.message;
if (reasonStr) {
userInfo[NSLocalizedDescriptionKey] = reasonStr;
}
error = [NSError errorWithDomain:kGTLRErrorObjectDomain
code:self.code.integerValue
userInfo:userInfo];
}
return error;
}
+ (GTLRErrorObject *)underlyingObjectForError:(NSError *)foundationError {
NSDictionary *userInfo = [foundationError userInfo];
GTLRErrorObject *errorObj = [userInfo objectForKey:kGTLRStructuredErrorKey];
return errorObj;
}
- (BOOL)isEqual:(id)object {
// Include the underlying foundation error in equality checks.
if (self == object) return YES;
if (![super isEqual:object]) return NO;
if (![object isKindOfClass:[GTLRErrorObject class]]) return NO;
GTLRErrorObject *other = (GTLRErrorObject *)object;
return GTLR_AreEqualOrBothNil(_originalFoundationError,
other->_originalFoundationError);
}
+ (BOOL)supportsSecureCoding {
return YES;
}
- (instancetype)initWithCoder:(NSCoder *)decoder {
self = [super initWithCoder:decoder];
if (self) {
_originalFoundationError =
[decoder decodeObjectOfClass:[NSError class]
forKey:kGTLRErrorObjectFoundationErrorKey];
}
return self;
}
- (void)encodeWithCoder:(NSCoder *)encoder {
[super encodeWithCoder:encoder];
[encoder encodeObject:_originalFoundationError
forKey:kGTLRErrorObjectFoundationErrorKey];
}
@end
@implementation GTLRErrorObjectErrorItem
@dynamic domain;
@dynamic reason;
@dynamic message;
@dynamic location;
@end
@implementation GTLRErrorObjectDetail
@dynamic type;
@dynamic detail;
+ (NSDictionary *)propertyToJSONKeyMap {
return @{ @"type" : @"@type" };
}
@end
/* Copyright (c) 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Query documentation:
// https://github.com/google/google-api-objectivec-client-for-rest/wiki#query-operations
#import "GTLRObject.h"
#import "GTLRUploadParameters.h"
NS_ASSUME_NONNULL_BEGIN
@class GTLRServiceTicket;
@class GTLRServiceExecutionParameters;
@class GTLRQuery;
/**
* This protocol is just to support passing of either a batch or a single query
* to a GTLRService instance. The library does not expect or support client app
* implementations of this protocol.
*/
@protocol GTLRQueryProtocol <NSObject, NSCopying>
/**
* Service ticket values may be set in the execution parameters for an individual query
* prior to executing the query.
*/
@property(atomic, strong, null_resettable) GTLRServiceExecutionParameters *executionParameters;
- (BOOL)isBatchQuery;
- (BOOL)hasExecutionParameters;
- (BOOL)shouldSkipAuthorization;
- (void)invalidateQuery;
- (nullable NSDictionary<NSString *, NSString *> *)additionalHTTPHeaders;
- (nullable NSDictionary<NSString *, NSString *> *)additionalURLQueryParameters;
- (nullable NSString *)loggingName;
- (nullable GTLRUploadParameters *)uploadParameters;
@end
@protocol GTLRQueryCollectionProtocol
@optional
@property(nonatomic, strong) NSString *pageToken;
@end
/**
* A block called when a query completes executing.
*
* Errors passed to the completionBlock will have an "underlying" GTLRErrorObject
* when the server returned an error for this specific query:
*
* GTLRErrorObject *errorObj = [GTLRErrorObject underlyingObjectForError:callbackError];
* if (errorObj) {
* // The server returned this error for this specific query.
* } else {
* // The query execution fetch failed.
* }
*
* @param callbackTicket The ticket that tracked query execution.
* @param object The result of query execution. This will be derived from
* GTLRObject.
* @param callbackError If non-nil, the query execution failed.
*/
typedef void (^GTLRQueryCompletionBlock)(GTLRServiceTicket *callbackTicket,
id _Nullable object,
NSError * _Nullable callbackError);
/**
* Class for a single query.
*/
@interface GTLRQuery : NSObject <GTLRQueryProtocol, NSCopying>
/**
* The object to be uploaded with the query. The JSON of this object becomes
* the body for PUT and POST requests.
*/
@property(atomic, strong, nullable) GTLRObject *bodyObject;
/**
* Each query must have a request ID string. The client app may replace the
* default assigned request ID with a custom string, provided that if
* used in a batch query, all request IDs in the batch must be unique.
*/
@property(atomic, copy) NSString *requestID;
/**
* For queries which support file upload, the MIME type and file URL
* or data must be provided.
*/
@property(atomic, copy, nullable) GTLRUploadParameters *uploadParameters;
/**
* Any additional URL query parameters for this query.
*
* These query parameters override the same keys from the service object's
* additionalURLQueryParameters
*/
@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalURLQueryParameters;
/**
* Any additional HTTP headers for this query.
*
* These headers override the same keys from the service object's additionalHTTPHeaders
*/
@property(atomic, copy, nullable) NSDictionary<NSString *, NSString *> *additionalHTTPHeaders;
/**
* If set, when the query is executed, an @c "alt" query parameter is added
* with this value and the raw result of the query is returned in a
* GTLRDataObject. This is useful when the server documents result datatypes
* other than JSON ("csv", for example).
*/
@property(atomic, copy) NSString *downloadAsDataObjectType;
/**
* If set, and the query also has a non-empty @c downloadAsDataObjectType, the
* URL to download from will be modified to include "download/". This extra path
* component avoids the need for a server redirect to the download URL.
*/
@property(atomic, assign) BOOL useMediaDownloadService;
/**
* Clients may set this to YES to disallow authorization. Defaults to NO.
*/
@property(atomic, assign) BOOL shouldSkipAuthorization;
/**
* An optional callback block to be called immediately before the executeQuery: completion handler.
*
* The completionBlock property is particularly useful for queries executed in a batch.
*/
@property(atomic, copy, nullable) GTLRQueryCompletionBlock completionBlock;
/**
* The brief string to identify this query in GTMSessionFetcher http logs.
*
* A default logging name is set by the code generator, but may be overridden by the client app.
*/
@property(atomic, copy, nullable) NSString *loggingName;
#pragma mark Internal
/////////////////////////////////////////////////////////////////////////////////////////////
//
// Properties below are used by the library and aren't typically needed by client apps.
//
/////////////////////////////////////////////////////////////////////////////////////////////
/**
* The URITemplate path segment. This is initialized in by the service generator.
*/
@property(atomic, readonly) NSString *pathURITemplate;
/**
* The HTTP method to use for this query. This is initialized in by the service generator.
*/
@property(atomic, readonly, nullable) NSString *httpMethod;
/**
* The parameters names that are in the URI Template.
* This is initialized in by the service generator.
*
* The service generator collects these via the discovery info instead of having to parse the
* template to figure out what is part of the path.
*/
@property(atomic, readonly, nullable) NSArray<NSString *> *pathParameterNames;
/**
* The JSON dictionary of all the parameters set on this query.
*
* The JSON values are set by setting the query's properties.
*/
@property(nonatomic, strong, nullable) NSMutableDictionary<NSString *, id> *JSON;
/**
* A custom URI template for resumable uploads. This is initialized by the service generator
* if needed.
*/
@property(atomic, copy, nullable) NSString *resumableUploadPathURITemplateOverride;
/**
* A custom URI template for simple and multipart media uploads. This is initialized
* by the service generator.
*/
@property(atomic, copy, nullable) NSString *simpleUploadPathURITemplateOverride;
/**
* The GTLRObject subclass expected for results. This is initialized by the service generator.
*
* This is needed if the object returned by the server lacks a known "kind" string.
*/
@property(atomic, assign, nullable) Class expectedObjectClass;
/**
* Set when the query has been invalidated, meaning it was slated for execution so it's been copied
* and its callbacks were released, or it's a copy that has finished executing.
*
* Once a query has been invalidated, it cannot be executed, added to a batch, or copied.
*/
@property(atomic, assign, getter=isQueryInvalid) BOOL queryInvalid;
/**
* Internal query init method.
*
* @param pathURITemplate URI template to be filled in with parameters.
* @param httpMethod The requests's http method. A nil method will execute as GET.
* @param pathParameterNames Names of parameters to be replaced in the template.
*/
- (instancetype)initWithPathURITemplate:(NSString *)pathURITemplate
HTTPMethod:(nullable NSString *)httpMethod
pathParameterNames:(nullable NSArray<NSString *> *)pathParameterNames NS_DESIGNATED_INITIALIZER;
/**
* @return Auto-generated request ID string.
*/
+ (NSString *)nextRequestID;
/**
* Overridden by subclasses.
*
* @return Substitute parameter names where needed for Objective-C or library compatibility.
*/
+ (nullable NSDictionary<NSString *, NSString *> *)parameterNameMap;
/**
* Overridden by subclasses.
*
* @return Map of property keys to specifying the class of objects to be instantiated in arrays.
*/
+ (nullable NSDictionary<NSString *, Class> *)arrayPropertyToClassMap;
- (instancetype)init NS_UNAVAILABLE;
@end
/**
* The library doesn't use GTLRQueryCollectionImpl, but it provides a concrete implementation
* of the protocol so the methods do not cause private method errors in Xcode/AppStore review.
*/
@interface GTLRQueryCollectionImpl : GTLRQuery <GTLRQueryCollectionProtocol>
@end
NS_ASSUME_NONNULL_END
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
This source diff could not be displayed because it is too large. You can view the blob instead.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment