shadow categories action button
|
@ -0,0 +1,52 @@
|
||||||
|
# Miscellaneous
|
||||||
|
*.class
|
||||||
|
*.log
|
||||||
|
*.pyc
|
||||||
|
*.swp
|
||||||
|
.DS_Store
|
||||||
|
.atom/
|
||||||
|
.buildlog/
|
||||||
|
.history
|
||||||
|
.svn/
|
||||||
|
|
||||||
|
# IntelliJ related
|
||||||
|
*.iml
|
||||||
|
*.ipr
|
||||||
|
*.iws
|
||||||
|
.idea/
|
||||||
|
|
||||||
|
# The .vscode folder contains launch configuration and tasks you configure in
|
||||||
|
# VS Code which you may wish to be included in version control, so this line
|
||||||
|
# is commented out by default.
|
||||||
|
#.vscode/
|
||||||
|
|
||||||
|
# Flutter/Dart/Pub related
|
||||||
|
**/doc/api/
|
||||||
|
**/ios/Flutter/.last_build_id
|
||||||
|
.dart_tool/
|
||||||
|
.flutter-plugins
|
||||||
|
.flutter-plugins-dependencies
|
||||||
|
.packages
|
||||||
|
.pub-cache/
|
||||||
|
.pub/
|
||||||
|
/build/
|
||||||
|
|
||||||
|
# Web related
|
||||||
|
lib/generated_plugin_registrant.dart
|
||||||
|
|
||||||
|
# Symbolication related
|
||||||
|
app.*.symbols
|
||||||
|
|
||||||
|
# Obfuscation related
|
||||||
|
app.*.map.json
|
||||||
|
|
||||||
|
# Android Studio will place build artifacts here
|
||||||
|
/android/app/debug
|
||||||
|
/android/app/profile
|
||||||
|
/android/app/release
|
||||||
|
|
||||||
|
# PROJECT
|
||||||
|
/go/mobile/lib/*.aar
|
||||||
|
/go/mobile/lib/*.jar
|
||||||
|
/go/mobile/lib/*.framework/
|
||||||
|
/go/vendor/
|
|
@ -0,0 +1,10 @@
|
||||||
|
# This file tracks properties of this Flutter project.
|
||||||
|
# Used by Flutter tool to assess capabilities and perform upgrades etc.
|
||||||
|
#
|
||||||
|
# This file should be version controlled and should not be manually edited.
|
||||||
|
|
||||||
|
version:
|
||||||
|
revision: f4abaa0735eba4dfd8f33f73363911d63931fe03
|
||||||
|
channel: stable
|
||||||
|
|
||||||
|
project_type: app
|
|
@ -0,0 +1,20 @@
|
||||||
|
Copyright (c) 2021-2021 niuhuan
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining
|
||||||
|
a copy of this software and associated documentation files (the
|
||||||
|
"Software"), to deal in the Software without restriction, including
|
||||||
|
without limitation the rights to use, copy, modify, merge, publish,
|
||||||
|
distribute, sublicense, and/or sell copies of the Software, and to
|
||||||
|
permit persons to whom the Software is furnished to do so, subject to
|
||||||
|
the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be
|
||||||
|
included in all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
||||||
|
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
||||||
|
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
||||||
|
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
||||||
|
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
148
README.md
|
@ -0,0 +1,148 @@
|
||||||
|
PIKAPI - 漫画客户端
|
||||||
|
========
|
||||||
|
[![license](https://img.shields.io/github/license/niuhuan/pikapi-flutter)](https://raw.githubusercontent.com/niuhuan/pikapi-flutter/master/LICENSE)
|
||||||
|
[![releases](https://img.shields.io/github/v/release/niuhuan/pikapi-flutter)](https://github.com/niuhuan/pikapi-flutter/releases)
|
||||||
|
[![downloads](https://img.shields.io/github/downloads/niuhuan/pikapi-flutter/total)](https://github.com/niuhuan/pikapi-flutter/releases)
|
||||||
|
|
||||||
|
- 美观易用且无广告的漫画客户端, 能运行在Windows/MacOS/Linux/Android/IOS中。
|
||||||
|
- 本仓库仅作为学习交流使用, 请您遵守当地法律法规以及开源协议。
|
||||||
|
- 您的star和issue是对开发者的莫大鼓励, 可以源仓库下载最新的源码/安装包, 表示支持/提出建议。
|
||||||
|
- 源仓库地址 [https://github.com/niuhuan/pikapi-flutter](https://github.com/niuhuan/pikapi-flutter)
|
||||||
|
- 此项目仅接受简体中文的issues。
|
||||||
|
|
||||||
|
## 界面 / 功能
|
||||||
|
|
||||||
|
![阅读器](images/reader.png)
|
||||||
|
|
||||||
|
### 分流
|
||||||
|
|
||||||
|
VPN->代理->分流, 这三个功能如果同时设置, 您会在您手机的VPN上访问代理, 使用代理请求分流服务器。
|
||||||
|
|
||||||
|
### 漫画分类/搜索
|
||||||
|
|
||||||
|
![分类](images/categories_screen.png) ![列表](images/comic_list.png)
|
||||||
|
|
||||||
|
### 漫画阅读/下载/导入/导出
|
||||||
|
|
||||||
|
您可以在除IOS外导出任意已经完成的下载到zip, 从另外一台设备导入。 导出的zip解压后可以直接使用其中的HTML进行阅读
|
||||||
|
|
||||||
|
![导出下载](images/exporting.png)
|
||||||
|
|
||||||
|
![HTML预览](images/exporting2.png)
|
||||||
|
|
||||||
|
### 游戏
|
||||||
|
|
||||||
|
![games](images/games.png)
|
||||||
|
![game](images/game.png)
|
||||||
|
|
||||||
|
## 特性
|
||||||
|
|
||||||
|
- [x] 用户
|
||||||
|
- [x] 登录 / 注册 / 获取个人信息 / 自动打卡
|
||||||
|
- [x] 漫画
|
||||||
|
- [x] 分类 / 搜索 / 随机本子 / 看此本子的也在看 / 排行榜
|
||||||
|
- [x] 在分类中搜索 / 按 "分类 / 标签 / 创建人 / 汉化组" 检索
|
||||||
|
- [x] 漫画详情 / 章节 / 看图 / 将图片保存到相册
|
||||||
|
- [x] 收藏 / 喜欢
|
||||||
|
- [x] 获取评论 / 评论 / 评论回复 (社区评论后无法删除, 请谨慎使用)
|
||||||
|
- [x] 游戏
|
||||||
|
- [x] 列表 / 详情 / 无广告下载
|
||||||
|
- [x] 下载
|
||||||
|
- [x] 导入导出 / 无线共享 / 移动设备与PC设备传输
|
||||||
|
- [ ] 聊天室
|
||||||
|
- [x] 缓存 / 清理
|
||||||
|
- [x] 设备支持
|
||||||
|
- [x] 安卓
|
||||||
|
- [x] 高刷新频率屏幕适配 (90/120/144... Hz)
|
||||||
|
- [x] 安卓10以上随系统进入深色/夜间模式
|
||||||
|
|
||||||
|
## 其他说明
|
||||||
|
|
||||||
|
- 在ios/android环境 数据文件将会保存在程序自身数据目录中, 删除就会清理
|
||||||
|
- 在 windows 数据文件将会保存在程序同一目录
|
||||||
|
- 在 macos 数据文件将会"~/Library/Application Support/pikapi"
|
||||||
|
- 在 linux 数据文件将会"~/.pikapi"
|
||||||
|
|
||||||
|
## 运行 / 构建
|
||||||
|
|
||||||
|
这个应用程序使用golang和dart(flutter)作为主要语言, 可以兼容Windows, linux, MacOS, Android, IOS
|
||||||
|
|
||||||
|
使用了不同的框架桥接到桌面和移动平台上
|
||||||
|
|
||||||
|
- go-flutter => Windows / MacOS / Linux
|
||||||
|
- gomobile => Android / IOS
|
||||||
|
|
||||||
|
![平台](images/platforms.png)
|
||||||
|
|
||||||
|
### 开发环境准备
|
||||||
|
|
||||||
|
- [golang](https://golang.org/) (1.16以上版本)
|
||||||
|
- [flutter](https://flutter.dev/) (桌面端 Tag 2.2.3 以兼容hover)
|
||||||
|
|
||||||
|
### 环境配置
|
||||||
|
|
||||||
|
- 将~/go/bin (GoPath/bin) 设置到PATH环境变量内
|
||||||
|
- golang开启模块化
|
||||||
|
- 设置GoProxy (可选,在中国大陆网络建议设置)
|
||||||
|
- 参考地址 [https://goproxy.cn/](https://goproxy.cn/)
|
||||||
|
|
||||||
|
### 桌面平台 (go-flutter)
|
||||||
|
|
||||||
|
- [安装hover(go-flutter编译脚手架)](https://github.com/go-flutter-desktop/hover)
|
||||||
|
```shell
|
||||||
|
GO111MODULE=on go get -u -a github.com/go-flutter-desktop/hover
|
||||||
|
```
|
||||||
|
- 执行编译命令 ($system替换为windows/darwin等)
|
||||||
|
```shell
|
||||||
|
hover run
|
||||||
|
hover build $system
|
||||||
|
```
|
||||||
|
|
||||||
|
### Linux的附加说明
|
||||||
|
|
||||||
|
- linux编译可能会遇到的问题
|
||||||
|
```shell
|
||||||
|
# No package 'gl' found
|
||||||
|
sudo apt install libgl1-mesa-dev
|
||||||
|
# X11/Xlib.h: No such file or directory
|
||||||
|
# 或者更多x11的头找不到等
|
||||||
|
sudo apt install xorg-dev
|
||||||
|
```
|
||||||
|
- 字体不显示
|
||||||
|
1. 将字体文件复制到项目目录下
|
||||||
|
```shell
|
||||||
|
cp /usr/share/fonts/truetype/droid/DroidSansFallbackFull.ttf fonts/
|
||||||
|
```
|
||||||
|
2. 设置flutter打包的字体
|
||||||
|
```yaml
|
||||||
|
fonts:
|
||||||
|
- family: Roboto
|
||||||
|
fonts:
|
||||||
|
- asset: fonts/DroidSansFallbackFull.ttf
|
||||||
|
```
|
||||||
|
|
||||||
|
### 移动端 (gomobile)
|
||||||
|
|
||||||
|
- [安装gomobile](https://github.com/golang/mobile)
|
||||||
|
```shell
|
||||||
|
go install golang.org/x/mobile/cmd/gomobile@latest
|
||||||
|
go get golang.org/x/mobile/cmd/gobind
|
||||||
|
```
|
||||||
|
- 执行编译命令 (bind-android.sh/bind-ios.sh根据平台选择, $system替换为apk/ipa等)
|
||||||
|
```shell
|
||||||
|
cd go/mobile
|
||||||
|
sh bind-ios.sh
|
||||||
|
sh bind-android.sh
|
||||||
|
cd ../../
|
||||||
|
flutter build $system
|
||||||
|
```
|
||||||
|
|
||||||
|
## 请您遵守使用规则
|
||||||
|
|
||||||
|
本软件或本软件的拓展, 个人或企业不可用于商业用途, 不可上架任何商店
|
||||||
|
|
||||||
|
拓展包括但是不限于以下内容
|
||||||
|
|
||||||
|
- 使用本软件进行继续开发形成的软件。
|
||||||
|
- 引入本软件部分内容为依赖/参考本软件/使用本软件内代码的同时, 包含本软件内一致内容或功能的软件。
|
||||||
|
- 直接对本软件进行打包发布
|
|
@ -0,0 +1,11 @@
|
||||||
|
gradle-wrapper.jar
|
||||||
|
/.gradle
|
||||||
|
/captures/
|
||||||
|
/gradlew
|
||||||
|
/gradlew.bat
|
||||||
|
/local.properties
|
||||||
|
GeneratedPluginRegistrant.java
|
||||||
|
|
||||||
|
# Remember to never publicly share your keystore.
|
||||||
|
# See https://flutter.dev/docs/deployment/android#reference-the-keystore-from-the-app
|
||||||
|
key.properties
|
|
@ -0,0 +1,64 @@
|
||||||
|
def localProperties = new Properties()
|
||||||
|
def localPropertiesFile = rootProject.file('local.properties')
|
||||||
|
if (localPropertiesFile.exists()) {
|
||||||
|
localPropertiesFile.withReader('UTF-8') { reader ->
|
||||||
|
localProperties.load(reader)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterRoot = localProperties.getProperty('flutter.sdk')
|
||||||
|
if (flutterRoot == null) {
|
||||||
|
throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
|
||||||
|
if (flutterVersionCode == null) {
|
||||||
|
flutterVersionCode = '1'
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutterVersionName = localProperties.getProperty('flutter.versionName')
|
||||||
|
if (flutterVersionName == null) {
|
||||||
|
flutterVersionName = '1.0'
|
||||||
|
}
|
||||||
|
|
||||||
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
|
||||||
|
|
||||||
|
android {
|
||||||
|
compileSdkVersion 30
|
||||||
|
|
||||||
|
sourceSets {
|
||||||
|
main.java.srcDirs += 'src/main/kotlin'
|
||||||
|
}
|
||||||
|
|
||||||
|
defaultConfig {
|
||||||
|
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
|
||||||
|
applicationId "niuhuan.pikapi"
|
||||||
|
minSdkVersion 16
|
||||||
|
targetSdkVersion 30
|
||||||
|
versionCode flutterVersionCode.toInteger()
|
||||||
|
versionName flutterVersionName
|
||||||
|
}
|
||||||
|
|
||||||
|
buildTypes {
|
||||||
|
release {
|
||||||
|
// TODO: Add your own signing config for the release build.
|
||||||
|
// Signing with the debug keys for now, so `flutter run --release` works.
|
||||||
|
signingConfig signingConfigs.debug
|
||||||
|
ndk {
|
||||||
|
abiFilters 'armeabi-v7a'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
flutter {
|
||||||
|
source '../..'
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
|
||||||
|
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.9'
|
||||||
|
implementation fileTree(dir: "../../go/mobile/lib", include: ["*.jar", "*.aar"])
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="niuhuan.pikapi">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
</manifest>
|
|
@ -0,0 +1,47 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="niuhuan.pikapi">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_INTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:label="pikapi"
|
||||||
|
android:icon="@mipmap/ic_launcher">
|
||||||
|
<activity
|
||||||
|
android:name=".MainActivity"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/LaunchTheme"
|
||||||
|
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
|
||||||
|
android:hardwareAccelerated="true"
|
||||||
|
android:windowSoftInputMode="adjustResize">
|
||||||
|
<!-- Specifies an Android theme to apply to this Activity as soon as
|
||||||
|
the Android process has started. This theme is visible to the user
|
||||||
|
while the Flutter UI initializes. After that, this theme continues
|
||||||
|
to determine the Window background behind the Flutter UI. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.NormalTheme"
|
||||||
|
android:resource="@style/NormalTheme"
|
||||||
|
/>
|
||||||
|
<!-- Displays an Android View that continues showing the launch screen
|
||||||
|
Drawable until Flutter paints its first frame, then this splash
|
||||||
|
screen fades out. A splash screen is useful to avoid any visual
|
||||||
|
gap between the end of Android's launch screen and the painting of
|
||||||
|
Flutter's first frame. -->
|
||||||
|
<meta-data
|
||||||
|
android:name="io.flutter.embedding.android.SplashScreenDrawable"
|
||||||
|
android:resource="@drawable/launch_background"
|
||||||
|
/>
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN"/>
|
||||||
|
<category android:name="android.intent.category.LAUNCHER"/>
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
<!-- Don't delete the meta-data below.
|
||||||
|
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
|
||||||
|
<meta-data
|
||||||
|
android:name="flutterEmbedding"
|
||||||
|
android:value="2"/>
|
||||||
|
</application>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,278 @@
|
||||||
|
package niuhuan.pikapi
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.content.res.Configuration
|
||||||
|
import android.graphics.Bitmap
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Environment
|
||||||
|
import android.os.Handler
|
||||||
|
import android.os.Looper
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import android.view.Display
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import androidx.annotation.NonNull
|
||||||
|
import io.flutter.embedding.android.FlutterActivity
|
||||||
|
import io.flutter.embedding.engine.FlutterEngine
|
||||||
|
import io.flutter.plugin.common.EventChannel
|
||||||
|
import io.flutter.plugin.common.MethodChannel
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.newSingleThreadContext
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import mobile.Mobile
|
||||||
|
import java.util.concurrent.Executors
|
||||||
|
|
||||||
|
class MainActivity : FlutterActivity() {
|
||||||
|
|
||||||
|
// 为什么换成换成线程池而不继续使用携程 : 下载图片速度慢会占满携程造成拥堵, 接口无法请求
|
||||||
|
private val pool = Executors.newCachedThreadPool { runnable ->
|
||||||
|
Thread(runnable).also { it.isDaemon = true }
|
||||||
|
}
|
||||||
|
private val uiThreadHandler = Handler(Looper.getMainLooper())
|
||||||
|
private val scope = CoroutineScope(newSingleThreadContext("worker-scope"))
|
||||||
|
|
||||||
|
private val notImplementedToken = Any()
|
||||||
|
private fun MethodChannel.Result.withCoroutine(exec: () -> Any?) {
|
||||||
|
pool.submit {
|
||||||
|
try {
|
||||||
|
val data = exec()
|
||||||
|
uiThreadHandler.post {
|
||||||
|
when (data) {
|
||||||
|
notImplementedToken -> {
|
||||||
|
notImplemented()
|
||||||
|
}
|
||||||
|
is Unit, null -> {
|
||||||
|
success(null)
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
success(data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
uiThreadHandler.post {
|
||||||
|
error("", e.message, "")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
|
||||||
|
super.configureFlutterEngine(flutterEngine)
|
||||||
|
Mobile.initApplication(context!!.filesDir.absolutePath)
|
||||||
|
// Method Channel
|
||||||
|
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, "method").setMethodCallHandler { call, result ->
|
||||||
|
result.withCoroutine {
|
||||||
|
when (call.method) {
|
||||||
|
"flatInvoke" -> {
|
||||||
|
Mobile.flatInvoke(
|
||||||
|
call.argument("method")!!,
|
||||||
|
call.argument("params")!!
|
||||||
|
)
|
||||||
|
}
|
||||||
|
"androidSaveFileToImage" -> {
|
||||||
|
saveImage(call.argument("path")!!)
|
||||||
|
}
|
||||||
|
"androidGetModes" -> {
|
||||||
|
modes()
|
||||||
|
}
|
||||||
|
"androidSetMode" -> {
|
||||||
|
setMode(call.argument("mode")!!)
|
||||||
|
}
|
||||||
|
"androidGetUiMode" -> {
|
||||||
|
uiMode()
|
||||||
|
}
|
||||||
|
"androidGetVersion" -> Build.VERSION.SDK_INT
|
||||||
|
else -> {
|
||||||
|
notImplementedToken
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
val eventMutex = Mutex()
|
||||||
|
var eventSink: EventChannel.EventSink? = null
|
||||||
|
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "flatEvent")
|
||||||
|
.setStreamHandler(object : EventChannel.StreamHandler {
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||||
|
events?.let { events ->
|
||||||
|
scope.launch {
|
||||||
|
eventMutex.lock()
|
||||||
|
eventSink = events
|
||||||
|
eventMutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
scope.launch {
|
||||||
|
eventMutex.lock()
|
||||||
|
eventSink = null
|
||||||
|
eventMutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
Mobile.eventNotify { message ->
|
||||||
|
scope.launch {
|
||||||
|
eventMutex.lock()
|
||||||
|
try {
|
||||||
|
eventSink?.let {
|
||||||
|
uiThreadHandler.post {
|
||||||
|
it.success(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
eventMutex.unlock()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "volume_button")
|
||||||
|
.setStreamHandler(volumeStreamHandler)
|
||||||
|
|
||||||
|
//
|
||||||
|
EventChannel(flutterEngine.dartExecutor.binaryMessenger, "ui_mode")
|
||||||
|
.setStreamHandler(uiModeStreamHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
// save_image
|
||||||
|
|
||||||
|
private fun saveImage(path: String) {
|
||||||
|
BitmapFactory.decodeFile(path)?.let { bitmap ->
|
||||||
|
val contentValues = ContentValues().apply {
|
||||||
|
put(MediaStore.MediaColumns.DISPLAY_NAME, System.currentTimeMillis().toString())
|
||||||
|
put(MediaStore.MediaColumns.MIME_TYPE, "image/jpeg")
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //this one
|
||||||
|
put(MediaStore.MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_PICTURES)
|
||||||
|
put(MediaStore.MediaColumns.IS_PENDING, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
contentResolver.insert(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentValues)?.let { uri ->
|
||||||
|
contentResolver.openOutputStream(uri)?.use { fos ->
|
||||||
|
bitmap.compress(Bitmap.CompressFormat.JPEG, 100, fos)
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { //this one
|
||||||
|
contentValues.clear()
|
||||||
|
contentValues.put(MediaStore.Video.Media.IS_PENDING, 0)
|
||||||
|
contentResolver.update(uri, contentValues, null, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// fps mods
|
||||||
|
private fun mixDisplay(): Display? {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
|
||||||
|
display?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
windowManager.defaultDisplay?.let {
|
||||||
|
return it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun modes(): List<String> {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
mixDisplay()?.let { display ->
|
||||||
|
return display.supportedModes.map { mode ->
|
||||||
|
mode.toString()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return ArrayList()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setMode(string: String) {
|
||||||
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||||
|
mixDisplay()?.let { display ->
|
||||||
|
return display.supportedModes.forEach { mode ->
|
||||||
|
if (mode.toString() == string) {
|
||||||
|
uiThreadHandler.post {
|
||||||
|
window.attributes = window.attributes.also { attr ->
|
||||||
|
attr.preferredDisplayModeId = mode.modeId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// volume_buttons
|
||||||
|
|
||||||
|
private var volumeEvents: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
private val volumeStreamHandler = object : EventChannel.StreamHandler {
|
||||||
|
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||||
|
volumeEvents = events
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
volumeEvents = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
|
||||||
|
volumeEvents?.let {
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
|
||||||
|
uiThreadHandler.post {
|
||||||
|
it.success("DOWN")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if (keyCode == KeyEvent.KEYCODE_VOLUME_UP) {
|
||||||
|
uiThreadHandler.post {
|
||||||
|
it.success("UP")
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return super.onKeyDown(keyCode, event)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ui_mode
|
||||||
|
|
||||||
|
private var uiModeEvents: EventChannel.EventSink? = null
|
||||||
|
|
||||||
|
private val uiModeStreamHandler = object : EventChannel.StreamHandler {
|
||||||
|
|
||||||
|
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
|
||||||
|
uiModeEvents = events
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onCancel(arguments: Any?) {
|
||||||
|
uiModeEvents = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onConfigurationChanged(newConfig: Configuration) {
|
||||||
|
when (newConfig.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||||
|
Configuration.UI_MODE_NIGHT_YES -> {
|
||||||
|
uiModeEvents?.let { it.success("NIGHT") }
|
||||||
|
}
|
||||||
|
Configuration.UI_MODE_NIGHT_NO -> {
|
||||||
|
uiModeEvents?.let { it.success("NORMAL") }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.onConfigurationChanged(newConfig)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun uiMode(): String {
|
||||||
|
return when (resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK) {
|
||||||
|
Configuration.UI_MODE_NIGHT_YES -> "NIGHT"
|
||||||
|
else -> "NORMAL"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="?android:colorBackground" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
|
@ -0,0 +1,12 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Modify this file to customize your launch splash screen -->
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:drawable="@android:color/white" />
|
||||||
|
|
||||||
|
<!-- You can insert your own image assets here -->
|
||||||
|
<!-- <item>
|
||||||
|
<bitmap
|
||||||
|
android:gravity="center"
|
||||||
|
android:src="@mipmap/launch_image" />
|
||||||
|
</item> -->
|
||||||
|
</layer-list>
|
After Width: | Height: | Size: 5.8 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 8.7 KiB |
After Width: | Height: | Size: 15 KiB |
After Width: | Height: | Size: 22 KiB |
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,18 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off -->
|
||||||
|
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<!-- Show a splash screen on the activity. Automatically removed when
|
||||||
|
Flutter draws its first frame -->
|
||||||
|
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||||
|
</style>
|
||||||
|
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||||
|
This theme determines the color of the Android Window while your
|
||||||
|
Flutter UI initializes, as well as behind your Flutter UI while its
|
||||||
|
running.
|
||||||
|
|
||||||
|
This Theme is only used starting with V2 of Flutter's Android embedding. -->
|
||||||
|
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||||
|
<item name="android:windowBackground">?android:colorBackground</item>
|
||||||
|
</style>
|
||||||
|
</resources>
|
|
@ -0,0 +1,7 @@
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="niuhuan.pikapi">
|
||||||
|
<!-- Flutter needs it to communicate with the running application
|
||||||
|
to allow setting breakpoints, to provide hot reload, etc.
|
||||||
|
-->
|
||||||
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
|
</manifest>
|
|
@ -0,0 +1,29 @@
|
||||||
|
buildscript {
|
||||||
|
ext.kotlin_version = '1.3.50'
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
classpath 'com.android.tools.build:gradle:4.1.0'
|
||||||
|
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
allprojects {
|
||||||
|
repositories {
|
||||||
|
google()
|
||||||
|
jcenter()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rootProject.buildDir = '../build'
|
||||||
|
subprojects {
|
||||||
|
project.buildDir = "${rootProject.buildDir}/${project.name}"
|
||||||
|
project.evaluationDependsOn(':app')
|
||||||
|
}
|
||||||
|
|
||||||
|
task clean(type: Delete) {
|
||||||
|
delete rootProject.buildDir
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
org.gradle.jvmargs=-Xmx1536M
|
||||||
|
android.useAndroidX=true
|
||||||
|
android.enableJetifier=true
|
|
@ -0,0 +1,6 @@
|
||||||
|
#Fri Jun 23 08:50:38 CEST 2017
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-all.zip
|
|
@ -0,0 +1,11 @@
|
||||||
|
include ':app'
|
||||||
|
|
||||||
|
def localPropertiesFile = new File(rootProject.projectDir, "local.properties")
|
||||||
|
def properties = new Properties()
|
||||||
|
|
||||||
|
assert localPropertiesFile.exists()
|
||||||
|
localPropertiesFile.withReader("UTF-8") { reader -> properties.load(reader) }
|
||||||
|
|
||||||
|
def flutterSdkPath = properties.getProperty("flutter.sdk")
|
||||||
|
assert flutterSdkPath != null, "flutter.sdk not set in local.properties"
|
||||||
|
apply from: "$flutterSdkPath/packages/flutter_tools/gradle/app_plugin_loader.gradle"
|
|
@ -0,0 +1,3 @@
|
||||||
|
build
|
||||||
|
.last_goflutter_check
|
||||||
|
.last_go-flutter_check
|
After Width: | Height: | Size: 33 KiB |
|
@ -0,0 +1,57 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"os"
|
||||||
|
"os/exec"
|
||||||
|
"path"
|
||||||
|
path2 "path"
|
||||||
|
"path/filepath"
|
||||||
|
"pgo/pikapi/config"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
applicationDir, err := os.UserHomeDir()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
switch runtime.GOOS {
|
||||||
|
case "windows":
|
||||||
|
// applicationDir = path.Join(applicationDir, "AppData", "Roaming")
|
||||||
|
file, err := exec.LookPath(os.Args[0])
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
path, err := filepath.Abs(file)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
i := strings.LastIndex(path, "/")
|
||||||
|
if i < 0 {
|
||||||
|
i = strings.LastIndex(path, "\\")
|
||||||
|
}
|
||||||
|
if i < 0 {
|
||||||
|
panic(errors.New(" can't find \"/\" or \"\\\""))
|
||||||
|
}
|
||||||
|
applicationDir = path2.Join(path[0:i+1], "data", "pikapi")
|
||||||
|
case "darwin":
|
||||||
|
applicationDir = path.Join(applicationDir, "Library", "Application Support", "pikapi")
|
||||||
|
case "linux":
|
||||||
|
applicationDir = path.Join(applicationDir, ".pikapi")
|
||||||
|
default:
|
||||||
|
panic(errors.New("not supported system"))
|
||||||
|
}
|
||||||
|
if _, err = os.Stat(applicationDir); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(applicationDir, os.FileMode(0700))
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
config.InitApplication(applicationDir)
|
||||||
|
}
|
|
@ -0,0 +1,66 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"github.com/go-flutter-desktop/go-flutter"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"pgo/pikapi/database/properties"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
// vmArguments may be set by hover at compile-time
|
||||||
|
var vmArguments string
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
// DO NOT EDIT, add options in options.go
|
||||||
|
mainOptions := []flutter.Option{
|
||||||
|
flutter.OptionVMArguments(strings.Split(vmArguments, ";")),
|
||||||
|
flutter.WindowIcon(iconProvider),
|
||||||
|
}
|
||||||
|
// 窗口初始化大小的处理
|
||||||
|
widthStr, _ := properties.LoadProperty("window_width", "600")
|
||||||
|
heightStr, _ := properties.LoadProperty("window_height", "900")
|
||||||
|
width, _ := strconv.Atoi(widthStr)
|
||||||
|
height, _ := strconv.Atoi(heightStr)
|
||||||
|
if width <= 0 {
|
||||||
|
width = 600
|
||||||
|
}
|
||||||
|
if height <= 0 {
|
||||||
|
height = 900
|
||||||
|
}
|
||||||
|
sizeOption := flutter.WindowInitialDimensions(width, height)
|
||||||
|
options = append(options, sizeOption)
|
||||||
|
//
|
||||||
|
err := flutter.Run(append(options, mainOptions...)...)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println(err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func iconProvider() ([]image.Image, error) {
|
||||||
|
execPath, err := os.Executable()
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to resolve executable path")
|
||||||
|
}
|
||||||
|
execPath, err = filepath.EvalSymlinks(execPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to eval symlinks for executable path")
|
||||||
|
}
|
||||||
|
imgFile, err := os.Open(filepath.Join(filepath.Dir(execPath), "assets", "icon.png"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to open assets/icon.png")
|
||||||
|
}
|
||||||
|
img, _, err := image.Decode(imgFile)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "failed to decode image")
|
||||||
|
}
|
||||||
|
return []image.Image{img}, nil
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/go-flutter-desktop/go-flutter"
|
||||||
|
"github.com/go-flutter-desktop/plugins/url_launcher"
|
||||||
|
"github.com/miguelpruivo/flutter_file_picker/go"
|
||||||
|
"pgo/cmd/plugin/pikapi"
|
||||||
|
)
|
||||||
|
|
||||||
|
var options = []flutter.Option{
|
||||||
|
flutter.AddPlugin(&pikapi.Plugin{}),
|
||||||
|
flutter.AddPlugin(&file_picker.FilePickerPlugin{}),
|
||||||
|
flutter.AddPlugin(&url_launcher.UrlLauncherPlugin{}),
|
||||||
|
}
|
|
@ -0,0 +1,72 @@
|
||||||
|
package pikapi
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/go-flutter-desktop/go-flutter/plugin"
|
||||||
|
"github.com/go-gl/glfw/v3.3/glfw"
|
||||||
|
"pgo/pikapi/controller"
|
||||||
|
"pgo/pikapi/database/properties"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var eventMutex = sync.Mutex{}
|
||||||
|
var eventSink *plugin.EventSink
|
||||||
|
|
||||||
|
type EventHandler struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EventHandler) OnListen(arguments interface{}, sink *plugin.EventSink) {
|
||||||
|
eventMutex.Lock()
|
||||||
|
defer eventMutex.Unlock()
|
||||||
|
eventSink = sink
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *EventHandler) OnCancel(arguments interface{}) {
|
||||||
|
eventMutex.Lock()
|
||||||
|
defer eventMutex.Unlock()
|
||||||
|
eventSink = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const channelName = "method"
|
||||||
|
|
||||||
|
type Plugin struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) InitPlugin(messenger plugin.BinaryMessenger) error {
|
||||||
|
|
||||||
|
channel := plugin.NewMethodChannel(messenger, channelName, plugin.StandardMethodCodec{})
|
||||||
|
|
||||||
|
channel.HandleFunc("flatInvoke", func(arguments interface{}) (interface{}, error) {
|
||||||
|
if argumentsMap, ok := arguments.(map[interface{}]interface{}); ok {
|
||||||
|
if method, ok := argumentsMap["method"].(string); ok {
|
||||||
|
if params, ok := argumentsMap["params"].(string); ok {
|
||||||
|
return controller.FlatInvoke(method, params)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, errors.New("params error")
|
||||||
|
})
|
||||||
|
|
||||||
|
exporting := plugin.NewEventChannel(messenger, "flatEvent", plugin.StandardMethodCodec{})
|
||||||
|
exporting.Handle(&EventHandler{})
|
||||||
|
|
||||||
|
controller.EventNotify = func(message string) {
|
||||||
|
eventMutex.Lock()
|
||||||
|
defer eventMutex.Unlock()
|
||||||
|
sink := eventSink
|
||||||
|
if sink != nil {
|
||||||
|
sink.Success(message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil // no error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *Plugin) InitPluginGLFW(window *glfw.Window) error {
|
||||||
|
window.SetSizeCallback(func(w *glfw.Window, width int, height int) {
|
||||||
|
properties.SaveProperty("window_width", strconv.Itoa(width))
|
||||||
|
properties.SaveProperty("window_height", strconv.Itoa(height))
|
||||||
|
})
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,17 @@
|
||||||
|
module pgo
|
||||||
|
|
||||||
|
go 1.16
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/PuerkitoBio/goquery v1.7.1
|
||||||
|
github.com/go-flutter-desktop/go-flutter v0.43.0
|
||||||
|
github.com/go-flutter-desktop/plugins/url_launcher v0.1.2
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265
|
||||||
|
github.com/miguelpruivo/flutter_file_picker/go v0.0.0-20210622152105-9f0a811028a0
|
||||||
|
github.com/niuhuan/pica-go v0.0.0-20210923020558-090104e7b1a7
|
||||||
|
github.com/pkg/errors v0.9.1
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 // indirect
|
||||||
|
gorm.io/driver/sqlite v1.1.4
|
||||||
|
gorm.io/gorm v1.21.12
|
||||||
|
)
|
|
@ -0,0 +1,81 @@
|
||||||
|
github.com/PuerkitoBio/goquery v1.7.1 h1:oE+T06D+1T7LNrn91B4aERsRIeCLJ/oPSa6xB9FPnz4=
|
||||||
|
github.com/PuerkitoBio/goquery v1.7.1/go.mod h1:XY0pP4kfraEmmV1O7Uf6XyjoslwsneBbgeDjLYuN8xY=
|
||||||
|
github.com/Xuanwo/go-locale v1.0.0 h1:oqC32Kyiu2XZq+fxtwEg0mWiv9WyDhyHu+sT5cDkgME=
|
||||||
|
github.com/Xuanwo/go-locale v1.0.0/go.mod h1:kB9tcLfr4Sp+ByIE9SE7vbUkXkGQqel2XH3EHpL0haA=
|
||||||
|
github.com/andybalholm/cascadia v1.2.0 h1:vuRCkM5Ozh/BfmsaTm26kbjm0mIOM3yS5Ek/F5h18aE=
|
||||||
|
github.com/andybalholm/cascadia v1.2.0/go.mod h1:YCyR8vOZT9aZ1CHEd8ap0gMVm2aFgxBp0T0eFw1RUQY=
|
||||||
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/gen2brain/dlgs v0.0.0-20190708095831-3854608588f7 h1:qA8Mdjwrlv/r/aMqArqO0IMHUiy6ApdW4+8DtKr7PvA=
|
||||||
|
github.com/gen2brain/dlgs v0.0.0-20190708095831-3854608588f7/go.mod h1:/eFcjDXaU2THSOOqLxOPETIbHETnamk8FA/hMjhg/gU=
|
||||||
|
github.com/go-flutter-desktop/go-flutter v0.30.0/go.mod h1:NCryd/AqiRbYSd8pMzQldYkgH1tZIFGt2ToUghZcWGA=
|
||||||
|
github.com/go-flutter-desktop/go-flutter v0.43.0 h1:7tdUbGKmHwdsUnBfC/h7zAO3T67cAkKSCWi9ZDFg25A=
|
||||||
|
github.com/go-flutter-desktop/go-flutter v0.43.0/go.mod h1:GSCn6XOpB0cnYlK9/BdSwxi99t5YD1XEk0v4agI7SS4=
|
||||||
|
github.com/go-flutter-desktop/plugins/url_launcher v0.1.2 h1:oFiIJjotMQvF8rfKWVJrf+1/JgTXShEIsibkiXrQnUw=
|
||||||
|
github.com/go-flutter-desktop/plugins/url_launcher v0.1.2/go.mod h1:GYgRDaLDAJRYvaASQk8HEmI8YJurbZGW5VVDIMxwzBU=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7 h1:SCYMcCJ89LjRGwEa0tRluNRiMjZHalQZrVrvTbPh+qw=
|
||||||
|
github.com/go-gl/gl v0.0.0-20190320180904-bf2b1f2f34d7/go.mod h1:482civXOzJJCPzJ4ZOX/pwvXBWSnzD4OKMdH4ClKGbk=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1 h1:QbL/5oDUmRBzO9/Z7Seo6zf912W/a6Sr4Eu0G/3Jho0=
|
||||||
|
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265 h1:BcbKYUZo/TKPsiSh7LymK3p+TNAJJW3OfGO/21sBbiA=
|
||||||
|
github.com/go-gl/glfw/v3.3/glfw v0.0.0-20201108214237-06ea97f0c265/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f h1:TyqzGm2z1h3AGhjOoRYyeLcW4WlW81MDQkWa+rx/000=
|
||||||
|
github.com/gopherjs/gopherjs v0.0.0-20190915194858-d3ddacdb130f/go.mod h1:wJfORRmW1u3UXTncJ5qlYoELFm8eSnnEO6hX4iZ3EWY=
|
||||||
|
github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E=
|
||||||
|
github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc=
|
||||||
|
github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI=
|
||||||
|
github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
|
||||||
|
github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.5 h1:1IdxlwTNazvbKJQSxoJ5/9ECbEeaTTyeU7sEAZ5KKTQ=
|
||||||
|
github.com/mattn/go-sqlite3 v1.14.5/go.mod h1:WVKg1VTActs4Qso6iwGbiFih2UIHo0ENGwNd0Lj+XmI=
|
||||||
|
github.com/miguelpruivo/flutter_file_picker/go v0.0.0-20210622152105-9f0a811028a0 h1:hXl9AMW20Php3xWlWZr2Acw50tqeblLgtLfLoRCACmA=
|
||||||
|
github.com/miguelpruivo/flutter_file_picker/go v0.0.0-20210622152105-9f0a811028a0/go.mod h1:csuW+TFyYKtiUwNvcvhcpyX4quPI7Pvv0SUogdqCW4I=
|
||||||
|
github.com/niuhuan/pica-go v0.0.0-20210923020558-090104e7b1a7 h1:E0WsH0UeFvuGiaEb1/tyy35ot76YDJKZ2q0/QjRQMWA=
|
||||||
|
github.com/niuhuan/pica-go v0.0.0-20210923020558-090104e7b1a7/go.mod h1:fx2m+OgMeEZf6/TrfblV9i85SjPsOGbnjIL2gohxP4M=
|
||||||
|
github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM=
|
||||||
|
github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s=
|
||||||
|
github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA=
|
||||||
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
|
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
||||||
|
github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0=
|
||||||
|
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
|
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b h1:+qEpEAPhDZ1o0x3tHzZTQDArnOixOzGD9HUJfcg0mb4=
|
||||||
|
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
|
||||||
|
golang.org/x/net v0.0.0-20180218175443-cbe0f9307d01/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
|
||||||
|
golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
|
||||||
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q=
|
||||||
|
golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||||
|
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
|
||||||
|
golang.org/x/sys v0.0.0-20200802091954-4b90ce9b60b3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007 h1:gG67DSER+11cZvqIMb8S8bt0vZtiN6xWYARwirrOSfE=
|
||||||
|
golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||||
|
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||||
|
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M=
|
||||||
|
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||||
|
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
|
||||||
|
golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gorm.io/driver/sqlite v1.1.4 h1:PDzwYE+sI6De2+mxAneV9Xs11+ZyKV6oxD3wDGkaNvM=
|
||||||
|
gorm.io/driver/sqlite v1.1.4/go.mod h1:mJCeTFr7+crvS+TRnWc5Z3UvwxUN1BGBLMrf5LA9DYw=
|
||||||
|
gorm.io/gorm v1.20.7/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw=
|
||||||
|
gorm.io/gorm v1.21.12 h1:3fQM0Eiz7jcJEhPggHEpoYnsGZqynMzverL77DV40RM=
|
||||||
|
gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0=
|
|
@ -0,0 +1,9 @@
|
||||||
|
#application-name: "pikapi" # Uncomment to modify this value.
|
||||||
|
#executable-name: "pikapi" # Uncomment to modify this value. Only lowercase a-z, numbers, underscores and no spaces
|
||||||
|
#package-name: "pikapi" # Uncomment to modify this value. Only lowercase a-z, numbers and no underscores or spaces
|
||||||
|
organization-name: "com.pikapi"
|
||||||
|
license: "" # MANDATORY: Fill in your SPDX license name: https://spdx.org/licenses
|
||||||
|
target: lib/main_desktop.dart
|
||||||
|
# opengl: "none" # Uncomment this line if you have trouble with your OpenGL driver (https://github.com/go-flutter-desktop/go-flutter/issues/272)
|
||||||
|
docker: false
|
||||||
|
engine-version: "" # change to a engine version commit
|
|
@ -0,0 +1 @@
|
||||||
|
gomobile bind -target=android/arm,android/arm64,android/386 -o lib/Pikapi.aar ./
|
|
@ -0,0 +1 @@
|
||||||
|
gomobile bind -target=android/arm -o lib/Pikapi.aar ./
|
|
@ -0,0 +1 @@
|
||||||
|
gomobile bind -target=ios -o lib/Pikapi.framework ./
|
|
@ -0,0 +1,22 @@
|
||||||
|
package mobile
|
||||||
|
|
||||||
|
import (
|
||||||
|
"pgo/pikapi/config"
|
||||||
|
"pgo/pikapi/controller"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitApplication(application string) {
|
||||||
|
config.InitApplication(application)
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlatInvoke(method string, params string) (string, error) {
|
||||||
|
return controller.FlatInvoke(method, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EventNotify(notify EventNotifyHandler) {
|
||||||
|
controller.EventNotify = notify.OnNotify
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventNotifyHandler interface {
|
||||||
|
OnNotify(message string)
|
||||||
|
}
|
|
@ -0,0 +1,38 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>English</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>{{.executableName}}</string>
|
||||||
|
<key>CFBundleGetInfoString</key>
|
||||||
|
<string>{{.description}}</string>
|
||||||
|
<key>CFBundleIconFile</key>
|
||||||
|
<string>icon.icns</string>
|
||||||
|
<key>NSHighResolutionCapable</key>
|
||||||
|
<true/>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>{{.organizationName}}.{{.packageName}}</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleLongVersionString</key>
|
||||||
|
<string>{{.version}}</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>{{.applicationName}}</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>APPL</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>{{.version}}</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>{{.organizationName}}.{{.packageName}}</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>{{.version}}</string>
|
||||||
|
<key>CSResourcesFileMapped</key>
|
||||||
|
<true/>
|
||||||
|
<key>NSHumanReadableCopyright</key>
|
||||||
|
<string></string>
|
||||||
|
<key>NSPrincipalClass</key>
|
||||||
|
<string>NSApplication</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,3 @@
|
||||||
|
#!/bin/sh
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
exec ./build/{{.executableName}}
|
|
@ -0,0 +1,9 @@
|
||||||
|
[Desktop Entry]
|
||||||
|
Version=1.0
|
||||||
|
Type=Application
|
||||||
|
Terminal=false
|
||||||
|
Categories=
|
||||||
|
Comment={{.description}}
|
||||||
|
Name={{.applicationName}}
|
||||||
|
Icon={{.iconPath}}
|
||||||
|
Exec={{.executablePath}}
|
|
@ -0,0 +1,29 @@
|
||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path"
|
||||||
|
"pgo/pikapi/controller"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"pgo/pikapi/database/network_cache"
|
||||||
|
"pgo/pikapi/database/properties"
|
||||||
|
"pgo/pikapi/utils"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitApplication 初始化文件保存的位置
|
||||||
|
func InitApplication(applicationDir string) {
|
||||||
|
println("初始化 : " + applicationDir)
|
||||||
|
var databasesDir, remoteDir, downloadDir, tmpDir string
|
||||||
|
databasesDir = path.Join(applicationDir, "databases")
|
||||||
|
remoteDir = path.Join(applicationDir, "pictures", "remote")
|
||||||
|
downloadDir = path.Join(applicationDir, "download")
|
||||||
|
tmpDir = path.Join(applicationDir, "download")
|
||||||
|
utils.Mkdir(databasesDir)
|
||||||
|
utils.Mkdir(remoteDir)
|
||||||
|
utils.Mkdir(downloadDir)
|
||||||
|
utils.Mkdir(tmpDir)
|
||||||
|
properties.InitDBConnect(databasesDir)
|
||||||
|
network_cache.InitDBConnect(databasesDir)
|
||||||
|
comic_center.InitDBConnect(databasesDir)
|
||||||
|
controller.InitClient()
|
||||||
|
controller.InitPlugin(remoteDir, downloadDir, tmpDir)
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package const_value
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/logger"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CreateDirMode = os.FileMode(0700)
|
||||||
|
CreateFileMode = os.FileMode(0600)
|
||||||
|
GormConfig = &gorm.Config{
|
||||||
|
Logger: logger.Default.LogMode(logger.Info),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
|
@ -0,0 +1,459 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
source "github.com/niuhuan/pica-go"
|
||||||
|
"net"
|
||||||
|
"net/http"
|
||||||
|
"net/url"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"pgo/pikapi/database/network_cache"
|
||||||
|
"pgo/pikapi/database/properties"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitClient() {
|
||||||
|
client.Timeout = time.Second * 60
|
||||||
|
switchAddress, _ = properties.LoadSwitchAddress()
|
||||||
|
proxy, _ := properties.LoadProxy()
|
||||||
|
changeProxyUrl(proxy)
|
||||||
|
}
|
||||||
|
|
||||||
|
var client = source.Client{}
|
||||||
|
var dialer = &net.Dialer{
|
||||||
|
Timeout: 30 * time.Second,
|
||||||
|
KeepAlive: 30 * time.Second,
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwitchAddress
|
||||||
|
// addr = "172.67.7.24:443"
|
||||||
|
// addr = "104.20.180.50:443"
|
||||||
|
// addr = "172.67.208.169:443"
|
||||||
|
var switchAddress = ""
|
||||||
|
var switchAddressPattern, _ = regexp.Compile("^.+picacomic\\.com:\\d+$")
|
||||||
|
|
||||||
|
func switchAddressContext(ctx context.Context, network, addr string) (net.Conn, error) {
|
||||||
|
if switchAddressPattern.MatchString(addr) && switchAddress != "" {
|
||||||
|
addr = switchAddress
|
||||||
|
}
|
||||||
|
return dialer.DialContext(ctx, network, addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
func changeProxyUrl(urlStr string) bool {
|
||||||
|
if urlStr == "" {
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
TLSHandshakeTimeout: time.Second * 10,
|
||||||
|
ExpectContinueTimeout: time.Second * 10,
|
||||||
|
ResponseHeaderTimeout: time.Second * 10,
|
||||||
|
IdleConnTimeout: time.Second * 10,
|
||||||
|
DialContext: switchAddressContext,
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
client.Transport = &http.Transport{
|
||||||
|
Proxy: func(_ *http.Request) (*url.URL, error) {
|
||||||
|
return url.Parse(urlStr)
|
||||||
|
},
|
||||||
|
TLSHandshakeTimeout: time.Second * 10,
|
||||||
|
ExpectContinueTimeout: time.Second * 10,
|
||||||
|
ResponseHeaderTimeout: time.Second * 10,
|
||||||
|
IdleConnTimeout: time.Second * 10,
|
||||||
|
DialContext: switchAddressContext,
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func cacheable(key string, expire time.Duration, reload func() (interface{}, error)) (string, error) {
|
||||||
|
// CACHE
|
||||||
|
cache := network_cache.LoadCache(key, expire)
|
||||||
|
if cache != "" {
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
// obj
|
||||||
|
obj, err := reload()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buff, err := json.Marshal(obj)
|
||||||
|
// push to cache
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// return
|
||||||
|
cache = string(buff)
|
||||||
|
network_cache.SaveCache(key, cache)
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func categories() (string, error) {
|
||||||
|
key := "CATEGORIES"
|
||||||
|
expire := time.Hour * 3
|
||||||
|
cache := network_cache.LoadCache(key, expire)
|
||||||
|
if cache != "" {
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
categories, err := client.Categories()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var dbCategories []comic_center.Category
|
||||||
|
for _, c := range categories {
|
||||||
|
dbCategories = append(dbCategories, comic_center.Category{
|
||||||
|
ID: c.Id,
|
||||||
|
Title: c.Title,
|
||||||
|
Description: c.Description,
|
||||||
|
IsWeb: c.IsWeb,
|
||||||
|
Active: c.Active,
|
||||||
|
Link: c.Link,
|
||||||
|
ThumbOriginalName: c.Thumb.OriginalName,
|
||||||
|
ThumbFileServer: c.Thumb.FileServer,
|
||||||
|
ThumbPath: c.Thumb.Path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
err = comic_center.UpSetCategories(&dbCategories)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buff, _ := json.Marshal(&categories)
|
||||||
|
cache = string(buff)
|
||||||
|
network_cache.SaveCache(key, cache)
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func comics(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Category string `json:"category"`
|
||||||
|
Tag string `json:"tag"`
|
||||||
|
CreatorId string `json:"creatorId"`
|
||||||
|
ChineseTeam string `json:"chineseTeam"`
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("COMICS$%s$%s$%s$%s$%s$%d", paramsStruct.Category, paramsStruct.Tag, paramsStruct.CreatorId, paramsStruct.ChineseTeam, paramsStruct.Sort, paramsStruct.Page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.Comics(paramsStruct.Category, paramsStruct.Tag, paramsStruct.CreatorId, paramsStruct.ChineseTeam, paramsStruct.Sort, paramsStruct.Page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func searchComics(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Categories []string `json:"categories"`
|
||||||
|
Keyword string `json:"keyword"`
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
categories := paramsStruct.Categories
|
||||||
|
keyword := paramsStruct.Keyword
|
||||||
|
sort := paramsStruct.Sort
|
||||||
|
page := paramsStruct.Page
|
||||||
|
//
|
||||||
|
var categoriesInKey string
|
||||||
|
if len(categories) == 0 {
|
||||||
|
categoriesInKey = ""
|
||||||
|
} else {
|
||||||
|
b, _ := json.Marshal(categories)
|
||||||
|
categoriesInKey = string(b)
|
||||||
|
}
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("SEARCH$%s$%s$%s$%d", categoriesInKey, keyword, sort, page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.SearchComics(categories, keyword, sort, page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func randomComics() (string, error) {
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("RANDOM"),
|
||||||
|
time.Millisecond*1,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.RandomComics()
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func leaderboard(typeName string) (string, error) {
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("LEADERBOARD$%s", typeName),
|
||||||
|
time.Second*200,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.Leaderboard(typeName)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func comicInfo(comicId string) (string, error) {
|
||||||
|
var err error
|
||||||
|
var comic *source.ComicInfo
|
||||||
|
// cache
|
||||||
|
key := fmt.Sprintf("COMIC_INFO$%s", comicId)
|
||||||
|
expire := time.Hour * 24 * 7
|
||||||
|
cache := network_cache.LoadCache(key, expire)
|
||||||
|
if cache != "" {
|
||||||
|
var co source.ComicInfo
|
||||||
|
err = json.Unmarshal([]byte(cache), &co)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
comic = &co
|
||||||
|
} else {
|
||||||
|
// get
|
||||||
|
comic, err = client.ComicInfo(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
var buff []byte
|
||||||
|
buff, err = json.Marshal(comic)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cache = string(buff)
|
||||||
|
network_cache.SaveCache(key, cache)
|
||||||
|
}
|
||||||
|
// 标记历史记录
|
||||||
|
view := comic_center.ComicView{}
|
||||||
|
view.ID = comicId
|
||||||
|
view.CreatedAt = comic.CreatedAt
|
||||||
|
view.UpdatedAt = comic.UpdatedAt
|
||||||
|
view.Title = comic.Title
|
||||||
|
view.Author = comic.Author
|
||||||
|
view.PagesCount = int32(comic.PagesCount)
|
||||||
|
view.EpsCount = int32(comic.EpsCount)
|
||||||
|
view.Finished = comic.Finished
|
||||||
|
c, _ := json.Marshal(comic.Categories)
|
||||||
|
view.Categories = string(c)
|
||||||
|
view.ThumbOriginalName = comic.Thumb.OriginalName
|
||||||
|
view.ThumbFileServer = comic.Thumb.FileServer
|
||||||
|
view.ThumbPath = comic.Thumb.Path
|
||||||
|
view.LikesCount = int32(comic.LikesCount)
|
||||||
|
view.Description = comic.Description
|
||||||
|
view.ChineseTeam = comic.ChineseTeam
|
||||||
|
t, _ := json.Marshal(comic.Tags)
|
||||||
|
view.Tags = string(t)
|
||||||
|
view.AllowDownload = comic.AllowDownload
|
||||||
|
view.ViewsCount = int32(comic.ViewsCount)
|
||||||
|
view.IsFavourite = comic.IsFavourite
|
||||||
|
view.IsLiked = comic.IsLiked
|
||||||
|
view.CommentsCount = int32(comic.CommentsCount)
|
||||||
|
err = comic_center.ViewComicUpdateInfo(&view)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// return
|
||||||
|
return cache, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ComicInfoCleanCache(comicId string) {
|
||||||
|
key := fmt.Sprintf("COMIC_INFO$%s", comicId)
|
||||||
|
network_cache.RemoveCache(key)
|
||||||
|
}
|
||||||
|
|
||||||
|
func epPage(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comicId := paramsStruct.ComicId
|
||||||
|
page := paramsStruct.Page
|
||||||
|
//
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("COMIC_EP_PAGE$%s$%d", comicId, page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.ComicEpPage(comicId, page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func comicPicturePageWithQuality(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
EpOrder int `json:"epOrder"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
Quality string `json:"quality"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comicId := paramsStruct.ComicId
|
||||||
|
epOrder := paramsStruct.EpOrder
|
||||||
|
page := paramsStruct.Page
|
||||||
|
quality := paramsStruct.Quality
|
||||||
|
//
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("COMIC_EP_PAGE$%s$%ds$%ds$%s", comicId, epOrder, page, quality),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.ComicPicturePageWithQuality(comicId, epOrder, page, quality)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func switchLike(comicId string) (string, error) {
|
||||||
|
point, err := client.SwitchLike(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// 更新viewLog里面的favour
|
||||||
|
comic_center.ViewComicUpdateLike(comicId, strings.HasPrefix(*point, "un"))
|
||||||
|
// 删除缓存
|
||||||
|
ComicInfoCleanCache(comicId)
|
||||||
|
return *point, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func switchFavourite(comicId string) (string, error) {
|
||||||
|
point, err := client.SwitchFavourite(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
// 更新viewLog里面的favour
|
||||||
|
comic_center.ViewComicUpdateFavourite(comicId, strings.HasPrefix(*point, "un"))
|
||||||
|
// 删除缓存
|
||||||
|
ComicInfoCleanCache(comicId)
|
||||||
|
return *point, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func favouriteComics(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Sort string `json:"sort"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
sort := paramsStruct.Sort
|
||||||
|
page := paramsStruct.Page
|
||||||
|
//
|
||||||
|
point, err := client.FavouriteComics(sort, page)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
str, err := json.Marshal(point)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(str), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func recommendation(comicId string) (string, error) {
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("RECOMMENDATION$%s", comicId),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.ComicRecommendation(comicId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func comments(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comicId := paramsStruct.ComicId
|
||||||
|
page := paramsStruct.Page
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("COMMENTS$%s$%d", comicId, page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.ComicCommentsPage(comicId, page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func commentChildren(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
CommentId string `json:"commentId"`
|
||||||
|
Page int `json:"page"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
commentId := paramsStruct.CommentId
|
||||||
|
page := paramsStruct.Page
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("COMMENT_CHILDREN$%s$%d", commentId, page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.CommentChildren(commentId, page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func postComment(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
err := client.PostComment(paramsStruct.ComicId, paramsStruct.Content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
network_cache.RemoveCaches("MY_COMMENTS$%")
|
||||||
|
network_cache.RemoveCaches(fmt.Sprintf("COMMENTS$%s$%%", paramsStruct.ComicId))
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func postChildComment(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
CommentId string `json:"commentId"`
|
||||||
|
Content string `json:"content"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
err := client.PostChildComment(paramsStruct.CommentId, paramsStruct.Content)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
network_cache.RemoveCaches(fmt.Sprintf("COMMENT_CHILDREN$%s$%%", paramsStruct.CommentId))
|
||||||
|
network_cache.RemoveCaches("MY_COMMENTS$%")
|
||||||
|
network_cache.RemoveCaches(fmt.Sprintf("COMMENTS$%s$%%", paramsStruct.ComicId))
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func myComments(pageStr string) (string, error) {
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("MY_COMMENTS$%d", page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.MyComments(page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func games(pageStr string) (string, error) {
|
||||||
|
page, err := strconv.Atoi(pageStr)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("GAMES$%d", page),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.GamePage(page)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func game(gameId string) (string, error) {
|
||||||
|
return cacheable(
|
||||||
|
fmt.Sprintf("GAME$%s", gameId),
|
||||||
|
time.Hour*2,
|
||||||
|
func() (interface{}, error) {
|
||||||
|
return client.GameInfo(gameId)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
|
@ -0,0 +1,45 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
)
|
||||||
|
|
||||||
|
var EventNotify func(message string)
|
||||||
|
|
||||||
|
func onEvent(function string, content string) {
|
||||||
|
event := EventNotify
|
||||||
|
if event != nil {
|
||||||
|
message := map[string]string{
|
||||||
|
"function": function,
|
||||||
|
"content": content,
|
||||||
|
}
|
||||||
|
buff, err := json.Marshal(message)
|
||||||
|
if err == nil {
|
||||||
|
event(string(buff))
|
||||||
|
} else {
|
||||||
|
print("SEND ERR?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadComicEventSend(comicDownload *comic_center.ComicDownload) {
|
||||||
|
buff, err := json.Marshal(comicDownload)
|
||||||
|
if err == nil {
|
||||||
|
onEvent("DOWNLOAD", string(buff))
|
||||||
|
} else {
|
||||||
|
print("SEND ERR?")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func notifyExport(str string) {
|
||||||
|
onEvent("EXPORT", str)
|
||||||
|
}
|
||||||
|
|
||||||
|
func serialize(point interface{}, err error) (string, error) {
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
buff, err := json.Marshal(point)
|
||||||
|
return string(buff), nil
|
||||||
|
}
|
|
@ -0,0 +1,307 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
utils2 "pgo/pikapi/utils"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var downloadRunning = false
|
||||||
|
var downloadRestart = false
|
||||||
|
|
||||||
|
var downloadingComic *comic_center.ComicDownload
|
||||||
|
var downloadingEp *comic_center.ComicDownloadEp
|
||||||
|
var downloadingPicture *comic_center.ComicDownloadPicture
|
||||||
|
|
||||||
|
func downloadBackground() {
|
||||||
|
println("后台线程启动")
|
||||||
|
go downloadBegin()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadBegin() {
|
||||||
|
time.Sleep(time.Second * 3)
|
||||||
|
go downloadLoadComic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadHasStop() bool {
|
||||||
|
if !downloadRunning {
|
||||||
|
go downloadBegin()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if downloadRestart {
|
||||||
|
downloadRestart = false
|
||||||
|
go downloadBegin()
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadDelete() bool {
|
||||||
|
c, e := comic_center.DeletingComic()
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
if c != nil {
|
||||||
|
os.RemoveAll(downloadPath(c.ID))
|
||||||
|
e = comic_center.TrueDelete(c.ID)
|
||||||
|
if e != nil {
|
||||||
|
panic(e)
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadLoadComic() {
|
||||||
|
for downloadDelete() {
|
||||||
|
}
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
downloadingComic, err = comic_center.LoadFirstNeedDownload()
|
||||||
|
// 查库有错误就停止
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go downloadInitComic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInitComic() {
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if downloadingComic == nil {
|
||||||
|
println("没有找到要下载的漫画")
|
||||||
|
go downloadBegin()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println("正在下载漫画 " + downloadingComic.Title)
|
||||||
|
downloadComicEventSend(downloadingComic)
|
||||||
|
eps, err := comic_center.ListDownloadEpByComicId(downloadingComic.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
for _, ep := range eps {
|
||||||
|
if !ep.FetchedPictures {
|
||||||
|
println("正在获取章节的图片 " + downloadingComic.Title + " " + ep.Title)
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
if client.Token == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err := downloadFetchPictures(&ep)
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
ep.FetchedPictures = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !ep.FetchedPictures {
|
||||||
|
println("章节的图片获取失败 " + downloadingComic.Title + " " + ep.Title)
|
||||||
|
err = comic_center.EpFailed(ep.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
println("章节的图片获取成功 " + downloadingComic.Title + " " + ep.Title)
|
||||||
|
downloadingComic.SelectedPictureCount = downloadingComic.SelectedPictureCount + ep.SelectedPictureCount
|
||||||
|
downloadComicEventSend(downloadingComic)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go downloadLoadEp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadFetchPictures(downloadEp *comic_center.ComicDownloadEp) error {
|
||||||
|
var list []comic_center.ComicDownloadPicture
|
||||||
|
page := 1
|
||||||
|
for true {
|
||||||
|
rsp, err := client.ComicPicturePage(downloadingComic.ID, int(downloadEp.EpOrder), page)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, doc := range rsp.Docs {
|
||||||
|
list = append(list, comic_center.ComicDownloadPicture{
|
||||||
|
ID: doc.Id,
|
||||||
|
ComicId: downloadEp.ComicId,
|
||||||
|
EpId: downloadEp.ID,
|
||||||
|
EpOrder: downloadEp.EpOrder,
|
||||||
|
OriginalName: doc.Media.OriginalName,
|
||||||
|
FileServer: doc.Media.FileServer,
|
||||||
|
Path: doc.Media.Path,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if rsp.Page.Page < rsp.Page.Pages {
|
||||||
|
page++
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
err := comic_center.FetchPictures(downloadEp.ComicId, downloadEp.ID, &list)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
downloadEp.SelectedPictureCount = int32(len(list))
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadLoadEp() {
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
downloadingEp, err = comic_center.LoadFirstNeedDownloadEp(downloadingComic.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go downloadInitEp()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInitEp() {
|
||||||
|
if downloadingEp == nil {
|
||||||
|
// 所有Ep都下完了, 汇总Download下载情况
|
||||||
|
go downloadSummaryDownload()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println("正在下载章节 " + downloadingEp.Title)
|
||||||
|
go downloadLoadPicture()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadSummaryDownload() {
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, err := comic_center.ListDownloadEpByComicId(downloadingComic.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
over := true
|
||||||
|
for _, downloadEp := range list {
|
||||||
|
over = over && downloadEp.DownloadFinished
|
||||||
|
}
|
||||||
|
if over {
|
||||||
|
err = comic_center.DownloadSuccess(downloadingComic.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
downloadingComic.DownloadFinished = true
|
||||||
|
downloadingComic.DownloadFinishedTime = time.Now()
|
||||||
|
} else {
|
||||||
|
err = comic_center.DownloadFailed(downloadingComic.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
downloadingComic.DownloadFailed = true
|
||||||
|
}
|
||||||
|
downloadComicEventSend(downloadingComic)
|
||||||
|
go downloadLoadComic()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadLoadPicture() {
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
var err error
|
||||||
|
downloadingPicture, err = comic_center.LoadFirstNeedDownloadPicture(downloadingEp.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
go downloadInitPicture()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadInitPicture() {
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if downloadingPicture == nil {
|
||||||
|
// 所有图片都下完了, 汇总EP下载情况
|
||||||
|
go downloadSummaryEp()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
println("正在下载图片 " + fmt.Sprintf("%d", downloadingPicture.RankInEp))
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
err := downloadThePicture(downloadingPicture)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
downloadingPicture.DownloadFinished = true
|
||||||
|
downloadingEp.DownloadPictureCount = downloadingEp.DownloadPictureCount + 1
|
||||||
|
downloadingComic.DownloadPictureCount = downloadingComic.DownloadPictureCount + 1
|
||||||
|
downloadComicEventSend(downloadingComic)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if !downloadingPicture.DownloadFinished {
|
||||||
|
err := comic_center.PictureFailed(downloadingPicture.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go downloadLoadPicture()
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadThePicture(picturePoint *comic_center.ComicDownloadPicture) error {
|
||||||
|
lock := utils2.HashLock(fmt.Sprintf("%s$%s", picturePoint.FileServer, picturePoint.Path))
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
picturePath := fmt.Sprintf("%s/%d/%d", picturePoint.ComicId, picturePoint.EpOrder, picturePoint.RankInEp)
|
||||||
|
realPath := downloadPath(picturePath)
|
||||||
|
// 从缓存
|
||||||
|
buff, img, format, err := decodeFromCache(picturePoint.FileServer, picturePoint.Path)
|
||||||
|
if err != nil {
|
||||||
|
// 从网络
|
||||||
|
buff, img, format, err = decodeFromUrl(picturePoint.FileServer, picturePoint.Path)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dir := filepath.Dir(realPath)
|
||||||
|
if _, err := os.Stat(dir); os.IsNotExist(err) {
|
||||||
|
os.Mkdir(dir, const_value.CreateDirMode)
|
||||||
|
}
|
||||||
|
err = ioutil.WriteFile(downloadPath(picturePath), buff, const_value.CreateFileMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return comic_center.PictureSuccess(
|
||||||
|
picturePoint.ComicId,
|
||||||
|
picturePoint.EpId,
|
||||||
|
picturePoint.ID,
|
||||||
|
int64(len(buff)),
|
||||||
|
format,
|
||||||
|
int32(img.Bounds().Dx()),
|
||||||
|
int32(img.Bounds().Dy()),
|
||||||
|
picturePath,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadSummaryEp() {
|
||||||
|
if downloadHasStop() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
list, err := comic_center.ListDownloadPictureByEpId(downloadingEp.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
over := true
|
||||||
|
for _, downloadPicture := range list {
|
||||||
|
over = over && downloadPicture.DownloadFinished
|
||||||
|
}
|
||||||
|
if over {
|
||||||
|
err = comic_center.EpSuccess(downloadingEp.ComicId, downloadingEp.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
err = comic_center.EpFailed(downloadingEp.ID)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
go downloadLoadEp()
|
||||||
|
}
|
|
@ -0,0 +1,475 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var exportingListener net.Listener
|
||||||
|
var exportingConn net.Conn
|
||||||
|
|
||||||
|
func exportComicUsingSocket(comicId string) (int, error) {
|
||||||
|
var err error
|
||||||
|
exportingListener, err = net.Listen("tcp", ":0")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
go handleExportingConn(comicId)
|
||||||
|
return exportingListener.Addr().(*net.TCPAddr).Port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleExportingConn(comicId string) {
|
||||||
|
defer exportingListener.Close()
|
||||||
|
var err error
|
||||||
|
exportingConn, err = exportingListener.Accept()
|
||||||
|
if err != nil {
|
||||||
|
notifyExport(fmt.Sprintf("导出失败"))
|
||||||
|
println(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer exportingConn.Close()
|
||||||
|
gw := gzip.NewWriter(exportingConn)
|
||||||
|
defer gw.Close()
|
||||||
|
tw := tar.NewWriter(gw)
|
||||||
|
defer tw.Close()
|
||||||
|
err = exportComicDownloadFetch(comicId, func(path string, size int64) (io.Writer, error) {
|
||||||
|
header := tar.Header{}
|
||||||
|
header.Name = path
|
||||||
|
header.Size = size
|
||||||
|
return tw, tw.WriteHeader(&header)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
notifyExport(fmt.Sprintf("导出失败"))
|
||||||
|
} else {
|
||||||
|
notifyExport(fmt.Sprintf("导出成功"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportComicUsingSocketExit() error {
|
||||||
|
if exportingConn != nil {
|
||||||
|
exportingConn.Close()
|
||||||
|
}
|
||||||
|
if exportingListener != nil {
|
||||||
|
exportingListener.Close()
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportComicDownload(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comicId := paramsStruct.ComicId
|
||||||
|
dir := paramsStruct.Dir
|
||||||
|
println(fmt.Sprintf("导出 %s 到 %s", comicId, dir))
|
||||||
|
comic, err := comic_center.FindComicDownloadById(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if comic == nil {
|
||||||
|
return errors.New("not found")
|
||||||
|
}
|
||||||
|
if !comic.DownloadFinished {
|
||||||
|
return errors.New("not download finish")
|
||||||
|
}
|
||||||
|
filePath := path.Join(dir, fmt.Sprintf("%s-%s.zip", comic.Title, time.Now().Format("2006_01_02_15_04_05.999")))
|
||||||
|
println(fmt.Sprintf("ZIP : %s", filePath))
|
||||||
|
fileStream, err := os.Create(filePath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer fileStream.Close()
|
||||||
|
zipWriter := zip.NewWriter(fileStream)
|
||||||
|
defer zipWriter.Close()
|
||||||
|
return exportComicDownloadFetch(comicId, func(path string, size int64) (io.Writer, error) {
|
||||||
|
header := tar.Header{}
|
||||||
|
header.Name = path
|
||||||
|
header.Size = size
|
||||||
|
return zipWriter.Create(path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func exportComicDownloadFetch(comicId string, onWriteFile func(path string, size int64) (io.Writer, error)) error {
|
||||||
|
comic, err := comic_center.FindComicDownloadById(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if comic == nil {
|
||||||
|
return errors.New("not found")
|
||||||
|
}
|
||||||
|
if !comic.DownloadFinished {
|
||||||
|
return errors.New("not download finish")
|
||||||
|
}
|
||||||
|
epList, err := comic_center.ListDownloadEpByComicId(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jsonComic := JsonComicDownload{}
|
||||||
|
jsonComic.ComicDownload = *comic
|
||||||
|
jsonComic.EpList = make([]JsonComicDownloadEp, 0)
|
||||||
|
for _, ep := range epList {
|
||||||
|
jsonEp := JsonComicDownloadEp{}
|
||||||
|
jsonEp.ComicDownloadEp = ep
|
||||||
|
jsonEp.PictureList = make([]JsonComicDownloadPicture, 0)
|
||||||
|
pictures, err := comic_center.ListDownloadPictureByEpId(ep.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, picture := range pictures {
|
||||||
|
jsonPicture := JsonComicDownloadPicture{}
|
||||||
|
jsonPicture.ComicDownloadPicture = picture
|
||||||
|
jsonPicture.SrcPath = fmt.Sprintf("pictures/%04d_%04d", ep.EpOrder, picture.RankInEp)
|
||||||
|
notifyExport(fmt.Sprintf("正在导出 EP:%d PIC:%d", ep.EpOrder, picture.RankInEp))
|
||||||
|
entryWriter, err := onWriteFile(jsonPicture.SrcPath, jsonPicture.FileSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
source, err := os.Open(downloadPath(picture.LocalPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = func() (int64, error) {
|
||||||
|
defer source.Close()
|
||||||
|
return io.Copy(entryWriter, source)
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jsonEp.PictureList = append(jsonEp.PictureList, jsonPicture)
|
||||||
|
}
|
||||||
|
jsonComic.EpList = append(jsonComic.EpList, jsonEp)
|
||||||
|
}
|
||||||
|
if comic.ThumbLocalPath != "" {
|
||||||
|
logoBuff, err := ioutil.ReadFile(downloadPath(comic.ThumbLocalPath))
|
||||||
|
if err == nil {
|
||||||
|
entryWriter, err := onWriteFile("logo", int64(len(logoBuff)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = entryWriter.Write(logoBuff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JS
|
||||||
|
{
|
||||||
|
buff, err := json.Marshal(&jsonComic)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logoBuff := append([]byte("data = "), buff...)
|
||||||
|
if err == nil {
|
||||||
|
entryWriter, err := onWriteFile("data.js", int64(len(logoBuff)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = entryWriter.Write(logoBuff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// HTML
|
||||||
|
{
|
||||||
|
var htmlBuff = []byte(indexHtml)
|
||||||
|
if err == nil {
|
||||||
|
entryWriter, err := onWriteFile("index.html", int64(len(htmlBuff)))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = entryWriter.Write(htmlBuff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("OK")
|
||||||
|
//
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexHtml = `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<style>
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
html, body {
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftNav {
|
||||||
|
position: fixed;
|
||||||
|
width: 350px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftNav > * {
|
||||||
|
display: inline-block;
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftNav > ul {
|
||||||
|
background: #333;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftNav > #slider {
|
||||||
|
margin-top: 1em;
|
||||||
|
float: right;
|
||||||
|
width: 40px;
|
||||||
|
border: none;
|
||||||
|
background: rgba(0, 0, 0, .4);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title > img {
|
||||||
|
display: block;
|
||||||
|
width: 80px;
|
||||||
|
margin: 30px auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title > p {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#title {
|
||||||
|
margin-bottom: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftNav > ul a {
|
||||||
|
margin: auto;
|
||||||
|
display: block;
|
||||||
|
color: white;
|
||||||
|
height: 40px;
|
||||||
|
line-height: 40px;
|
||||||
|
text-align: center;
|
||||||
|
width: 280px;
|
||||||
|
border-top: #666 solid 1px;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#leftNav > ul a:hover,#leftNav > ul a.active {
|
||||||
|
background: rgba(255, 255, 255, .1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#content {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content img {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<script src="data.js"></script>
|
||||||
|
<script>
|
||||||
|
function changeLeftNav() {
|
||||||
|
var doc = document.getElementById("leftNav")
|
||||||
|
if (doc.style.left) {
|
||||||
|
doc.style.left = ""
|
||||||
|
} else {
|
||||||
|
doc.style.left = "-300px"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeEp(epIndex) {
|
||||||
|
var ps = data.epList[epIndex].pictureList;
|
||||||
|
document.getElementById('content').innerHTML = "";
|
||||||
|
var d = document.createElement('div');
|
||||||
|
d.id = 'd';
|
||||||
|
document.getElementById('content').append(d);
|
||||||
|
for (var i = 0; i < ps.length; i++) {
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.src = ps[i].srcPath;
|
||||||
|
document.getElementById('content').append(img);
|
||||||
|
}
|
||||||
|
document.getElementById('d').scrollIntoView();
|
||||||
|
changeLeftNav();
|
||||||
|
var as = document.getElementById('leftNav').getElementsByTagName('a');
|
||||||
|
for (var i = 0; i < ps.length; i++) {
|
||||||
|
if(epIndex == i){
|
||||||
|
as[i].classList = ["active"];
|
||||||
|
}else{
|
||||||
|
as[i].className = "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="leftNav">
|
||||||
|
<ul>
|
||||||
|
<li id="title">
|
||||||
|
<script>
|
||||||
|
document.write('<img src="logo" /> <br/>')
|
||||||
|
document.write('<p>' + data.title + '</p>');
|
||||||
|
</script>
|
||||||
|
</li>
|
||||||
|
<script>
|
||||||
|
for (var i = 0; i < data.epList.length; i++) {
|
||||||
|
document.write('<li><a href="javascript:changeEp(' + i + ')">' + data.epList[i].title + '</a></li>');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</ul>
|
||||||
|
<button id="slider" onclick="changeLeftNav();">切换</button>
|
||||||
|
</div>
|
||||||
|
<div id="content">
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`
|
||||||
|
|
||||||
|
func exportComicDownloadToJPG(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comicId := paramsStruct.ComicId
|
||||||
|
dir := paramsStruct.Dir
|
||||||
|
println(fmt.Sprintf("导出 %s 到 %s", comicId, dir))
|
||||||
|
comic, err := comic_center.FindComicDownloadById(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if comic == nil {
|
||||||
|
return errors.New("not found")
|
||||||
|
}
|
||||||
|
if !comic.DownloadFinished {
|
||||||
|
return errors.New("not download finish")
|
||||||
|
}
|
||||||
|
dirPath := path.Join(dir, fmt.Sprintf("%s-%s", comic.Title, time.Now().Format("2006_01_02_15_04_05.999")))
|
||||||
|
println(fmt.Sprintf("DIR : %s", dirPath))
|
||||||
|
err = os.Mkdir(dirPath, const_value.CreateDirMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = os.Mkdir(path.Join(dirPath, "pictures"), const_value.CreateDirMode)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
epList, err := comic_center.ListDownloadEpByComicId(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
jsonComic := JsonComicDownload{}
|
||||||
|
jsonComic.ComicDownload = *comic
|
||||||
|
jsonComic.EpList = make([]JsonComicDownloadEp, 0)
|
||||||
|
for _, ep := range epList {
|
||||||
|
jsonEp := JsonComicDownloadEp{}
|
||||||
|
jsonEp.ComicDownloadEp = ep
|
||||||
|
jsonEp.PictureList = make([]JsonComicDownloadPicture, 0)
|
||||||
|
pictures, err := comic_center.ListDownloadPictureByEpId(ep.ID)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, picture := range pictures {
|
||||||
|
jsonPicture := JsonComicDownloadPicture{}
|
||||||
|
jsonPicture.ComicDownloadPicture = picture
|
||||||
|
jsonPicture.SrcPath = fmt.Sprintf("pictures/%04d_%04d.%s", ep.EpOrder, picture.RankInEp, picture.Format)
|
||||||
|
notifyExport(fmt.Sprintf("正在导出 EP:%d PIC:%d", ep.EpOrder, picture.RankInEp))
|
||||||
|
entryWriter, err := os.Create(path.Join(dirPath, jsonPicture.SrcPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = func() error {
|
||||||
|
defer entryWriter.Close()
|
||||||
|
source, err := os.Open(downloadPath(picture.LocalPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = func() (int64, error) {
|
||||||
|
defer source.Close()
|
||||||
|
return io.Copy(entryWriter, source)
|
||||||
|
}()
|
||||||
|
return err
|
||||||
|
}()
|
||||||
|
jsonEp.PictureList = append(jsonEp.PictureList, jsonPicture)
|
||||||
|
}
|
||||||
|
jsonComic.EpList = append(jsonComic.EpList, jsonEp)
|
||||||
|
}
|
||||||
|
if comic.ThumbLocalPath != "" {
|
||||||
|
logoBuff, err := ioutil.ReadFile(downloadPath(comic.ThumbLocalPath))
|
||||||
|
if err == nil {
|
||||||
|
entryWriter, err := os.Create(path.Join(dirPath, "logo"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer entryWriter.Close()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = entryWriter.Write(logoBuff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// JS
|
||||||
|
{
|
||||||
|
buff, err := json.Marshal(&jsonComic)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
logoBuff := append([]byte("data = "), buff...)
|
||||||
|
if err == nil {
|
||||||
|
|
||||||
|
entryWriter, err := os.Create(path.Join(dirPath, "data.js"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer entryWriter.Close()
|
||||||
|
_, err = entryWriter.Write(logoBuff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// HTML
|
||||||
|
{
|
||||||
|
var htmlBuff = []byte(indexHtml)
|
||||||
|
if err == nil {
|
||||||
|
entryWriter, err := os.Create(path.Join(dirPath, "index.html"))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer entryWriter.Close()
|
||||||
|
_, err = entryWriter.Write(htmlBuff)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
println("OK")
|
||||||
|
return nil
|
||||||
|
}
|
|
@ -0,0 +1,40 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/PuerkitoBio/goquery"
|
||||||
|
"net/http"
|
||||||
|
"regexp"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var downloadGameUrlPattern, _ = regexp.Compile("^https://game\\.eroge\\.xyz/hhh\\.php\\?id=\\d+$")
|
||||||
|
|
||||||
|
func downloadGame(url string) (string, error) {
|
||||||
|
if downloadGameUrlPattern.MatchString(url) {
|
||||||
|
return cacheable(fmt.Sprintf("GAME_PAGE$%s", url), time.Hour*1000, func() (interface{}, error) {
|
||||||
|
req, err := http.NewRequest("GET", url, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
req.Header.Set("user-agent", "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36")
|
||||||
|
rsp, err := client.Do(req)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer rsp.Body.Close()
|
||||||
|
doc, err := goquery.NewDocumentFromReader(rsp.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
find := doc.Find("a.layui-btn")
|
||||||
|
list := make([]string, find.Size())
|
||||||
|
find.Each(func(i int, selection *goquery.Selection) {
|
||||||
|
list[i] = selection.AttrOr("href", "")
|
||||||
|
})
|
||||||
|
return list, nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return "", errors.New("not support url")
|
||||||
|
}
|
|
@ -0,0 +1,96 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
_ "golang.org/x/image/webp"
|
||||||
|
"image"
|
||||||
|
_ "image/gif"
|
||||||
|
_ "image/jpeg"
|
||||||
|
_ "image/png"
|
||||||
|
"io/ioutil"
|
||||||
|
"net/http"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mutexCounter = -1
|
||||||
|
var busMutex *sync.Mutex
|
||||||
|
var subMutexes []*sync.Mutex
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
busMutex = &sync.Mutex{}
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
subMutexes = append(subMutexes, &sync.Mutex{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// takeMutex 下载图片获取一个锁, 这样只能同时下载5张图片
|
||||||
|
func takeMutex() *sync.Mutex {
|
||||||
|
busMutex.Lock()
|
||||||
|
defer busMutex.Unlock()
|
||||||
|
mutexCounter = (mutexCounter + 1) % len(subMutexes)
|
||||||
|
return subMutexes[mutexCounter]
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeInfoFromBuff(buff []byte) (image.Image, string, error) {
|
||||||
|
buffer := bytes.NewBuffer(buff)
|
||||||
|
return image.Decode(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
func decodeFromFile(path string) ([]byte, image.Image, string, error) {
|
||||||
|
b, e := ioutil.ReadFile(path)
|
||||||
|
if e != nil {
|
||||||
|
return nil, nil, "", e
|
||||||
|
}
|
||||||
|
i, f, e := decodeInfoFromBuff(b)
|
||||||
|
if e != nil {
|
||||||
|
return nil, nil, "", e
|
||||||
|
}
|
||||||
|
return b, i, f, e
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载图片并decode
|
||||||
|
func decodeFromUrl(fileServer string, path string) ([]byte, image.Image, string, error) {
|
||||||
|
m := takeMutex()
|
||||||
|
m.Lock()
|
||||||
|
defer m.Unlock()
|
||||||
|
request, err := http.NewRequest("GET", fileServer+"/static/"+path, nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
response, err := client.Do(request)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
defer response.Body.Close()
|
||||||
|
if response.StatusCode != 200 {
|
||||||
|
return nil, nil, "", errors.New("code is not 200")
|
||||||
|
}
|
||||||
|
buff, err := ioutil.ReadAll(response.Body)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
img, format, err := decodeInfoFromBuff(buff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
return buff, img, format, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// decodeFromCache 仅下载使用
|
||||||
|
func decodeFromCache(fileServer string, path string) ([]byte, image.Image, string, error) {
|
||||||
|
cache := comic_center.FindRemoteImage(fileServer, path)
|
||||||
|
if cache != nil {
|
||||||
|
buff, err := ioutil.ReadFile(remotePath(cache.LocalPath))
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
img, format, err := decodeInfoFromBuff(buff)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, "", err
|
||||||
|
}
|
||||||
|
return buff, img, format, err
|
||||||
|
}
|
||||||
|
return nil, nil, "", errors.New("not found")
|
||||||
|
}
|
|
@ -0,0 +1,197 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"archive/tar"
|
||||||
|
"archive/zip"
|
||||||
|
"compress/gzip"
|
||||||
|
"encoding/json"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
|
"os"
|
||||||
|
path2 "path"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"pgo/pikapi/utils"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func importComicDownloadUsingSocket(addr string) error {
|
||||||
|
//
|
||||||
|
conn, err := net.Dial("tcp", addr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
gr, err := gzip.NewReader(conn)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
tr := tar.NewReader(gr)
|
||||||
|
//
|
||||||
|
zipPath := path2.Join(tmpDir, "tmp.zip")
|
||||||
|
closed := false
|
||||||
|
zipFile, err := os.Create(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
if !closed {
|
||||||
|
zipFile.Close()
|
||||||
|
}
|
||||||
|
os.Remove(zipPath)
|
||||||
|
}()
|
||||||
|
zipWriter := zip.NewWriter(zipFile)
|
||||||
|
defer func() {
|
||||||
|
if !closed {
|
||||||
|
zipWriter.Close()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
//
|
||||||
|
for {
|
||||||
|
header, err := tr.Next()
|
||||||
|
if err == io.EOF {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if header.Typeflag != tar.TypeReg {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
writer, err := zipWriter.Create(header.Name)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, err = io.Copy(writer, tr)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err = zipWriter.Close()
|
||||||
|
zipFile.Close()
|
||||||
|
closed = true
|
||||||
|
return importComicDownload(zipPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
func importComicDownload(zipPath string) error {
|
||||||
|
zip, err := zip.OpenReader(zipPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer zip.Close()
|
||||||
|
dataJs, err := zip.Open("data.js")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer dataJs.Close()
|
||||||
|
dataBuff, err := ioutil.ReadAll(dataJs)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
data := strings.TrimLeft(string(dataBuff), "data = ")
|
||||||
|
var jsonComicDownload JsonComicDownload
|
||||||
|
err = json.Unmarshal([]byte(data), &jsonComicDownload)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return comic_center.Transaction(func(tx *gorm.DB) error {
|
||||||
|
// 删除
|
||||||
|
err := tx.Unscoped().Delete(&comic_center.ComicDownload{}, "id = ?", jsonComicDownload.ID).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Unscoped().Delete(&comic_center.ComicDownloadEp{}, "comic_id = ?", jsonComicDownload.ID).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Unscoped().Delete(&comic_center.ComicDownloadPicture{}, "comic_id = ?", jsonComicDownload.ID).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 插入
|
||||||
|
err = tx.Save(&jsonComicDownload.ComicDownload).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ep := range jsonComicDownload.EpList {
|
||||||
|
err = tx.Save(&ep.ComicDownloadEp).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, picture := range ep.PictureList {
|
||||||
|
notifyExport("事务 : " + picture.LocalPath)
|
||||||
|
err = tx.Save(&picture.ComicDownloadPicture).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// VIEW日志
|
||||||
|
view := comic_center.ComicView{}
|
||||||
|
view.ID = jsonComicDownload.ID
|
||||||
|
view.CreatedAt = jsonComicDownload.CreatedAt
|
||||||
|
view.UpdatedAt = jsonComicDownload.UpdatedAt
|
||||||
|
view.Title = jsonComicDownload.Title
|
||||||
|
view.Author = jsonComicDownload.Author
|
||||||
|
view.PagesCount = jsonComicDownload.PagesCount
|
||||||
|
view.EpsCount = jsonComicDownload.EpsCount
|
||||||
|
view.Finished = jsonComicDownload.Finished
|
||||||
|
c, _ := json.Marshal(jsonComicDownload.Categories)
|
||||||
|
view.Categories = string(c)
|
||||||
|
view.ThumbOriginalName = jsonComicDownload.ThumbOriginalName
|
||||||
|
view.ThumbFileServer = jsonComicDownload.ThumbFileServer
|
||||||
|
view.ThumbPath = jsonComicDownload.ThumbPath
|
||||||
|
view.LikesCount = 0
|
||||||
|
view.Description = jsonComicDownload.Description
|
||||||
|
view.ChineseTeam = jsonComicDownload.ChineseTeam
|
||||||
|
t, _ := json.Marshal(jsonComicDownload.Tags)
|
||||||
|
view.Tags = string(t)
|
||||||
|
view.AllowDownload = true
|
||||||
|
view.ViewsCount = 0
|
||||||
|
view.IsFavourite = false
|
||||||
|
view.IsLiked = false
|
||||||
|
view.CommentsCount = 0
|
||||||
|
err = comic_center.NoLockActionViewComicUpdateInfoDB(&view, tx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 覆盖文件
|
||||||
|
comicDirPath := downloadPath(jsonComicDownload.ID)
|
||||||
|
utils.Mkdir(comicDirPath)
|
||||||
|
logoReader, err := zip.Open("logo")
|
||||||
|
if err == nil {
|
||||||
|
defer logoReader.Close()
|
||||||
|
logoBuff, err := ioutil.ReadAll(logoReader)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
ioutil.WriteFile(path2.Join(comicDirPath, "logo"), logoBuff, const_value.CreateFileMode)
|
||||||
|
}
|
||||||
|
for _, ep := range jsonComicDownload.EpList {
|
||||||
|
utils.Mkdir(path2.Join(comicDirPath, strconv.Itoa(int(ep.EpOrder))))
|
||||||
|
for _, picture := range ep.PictureList {
|
||||||
|
notifyExport("写入 : " + picture.LocalPath)
|
||||||
|
zipEntry, err := zip.Open(picture.SrcPath)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = func() error {
|
||||||
|
defer zipEntry.Close()
|
||||||
|
entryBuff, err := ioutil.ReadAll(zipEntry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return ioutil.WriteFile(downloadPath(picture.LocalPath), entryBuff, const_value.CreateFileMode)
|
||||||
|
}()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 结束
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
|
@ -0,0 +1,31 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import "pgo/pikapi/database/comic_center"
|
||||||
|
|
||||||
|
type DisplayImageData struct {
|
||||||
|
FileSize int64 `json:"fileSize"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
Width int32 `json:"width"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
FinalPath string `json:"finalPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicDownloadPictureWithFinalPath struct {
|
||||||
|
comic_center.ComicDownloadPicture
|
||||||
|
FinalPath string `json:"finalPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JsonComicDownload struct {
|
||||||
|
comic_center.ComicDownload
|
||||||
|
EpList []JsonComicDownloadEp `json:"epList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JsonComicDownloadEp struct {
|
||||||
|
comic_center.ComicDownloadEp
|
||||||
|
PictureList []JsonComicDownloadPicture `json:"pictureList"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type JsonComicDownloadPicture struct {
|
||||||
|
comic_center.ComicDownloadPicture
|
||||||
|
SrcPath string `json:"srcPath"`
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func clientIpSet() (string, error) {
|
||||||
|
address, err := net.InterfaceAddrs()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
ipSet := make([]string, 0)
|
||||||
|
for _, address := range address {
|
||||||
|
// 检查ip地址判断是否回环地址
|
||||||
|
if ipNet, ok := address.(*net.IPNet); ok && !ipNet.IP.IsLoopback() {
|
||||||
|
if ipNet.IP.To4() != nil {
|
||||||
|
ipSet = append(ipSet, ipNet.IP.To4().String())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return strings.Join(ipSet, ","), nil
|
||||||
|
}
|
|
@ -0,0 +1,578 @@
|
||||||
|
package controller
|
||||||
|
|
||||||
|
import (
|
||||||
|
"crypto/md5"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
source "github.com/niuhuan/pica-go"
|
||||||
|
"image/jpeg"
|
||||||
|
"io/ioutil"
|
||||||
|
"os"
|
||||||
|
path2 "path"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"pgo/pikapi/database/comic_center"
|
||||||
|
"pgo/pikapi/database/network_cache"
|
||||||
|
"pgo/pikapi/database/properties"
|
||||||
|
"pgo/pikapi/utils"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
remoteDir string
|
||||||
|
downloadDir string
|
||||||
|
tmpDir string
|
||||||
|
)
|
||||||
|
|
||||||
|
func InitPlugin(_remoteDir string, _downloadDir string, _tmpDir string) {
|
||||||
|
remoteDir = _remoteDir
|
||||||
|
downloadDir = _downloadDir
|
||||||
|
tmpDir = _tmpDir
|
||||||
|
comic_center.ResetAll()
|
||||||
|
go downloadBackground()
|
||||||
|
downloadRunning = true
|
||||||
|
}
|
||||||
|
|
||||||
|
func remotePath(path string) string {
|
||||||
|
return path2.Join(remoteDir, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadPath(path string) string {
|
||||||
|
return path2.Join(downloadDir, path)
|
||||||
|
}
|
||||||
|
|
||||||
|
func saveProperty(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
Value string `json:"value"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
return properties.SaveProperty(paramsStruct.Name, paramsStruct.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadProperty(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
DefaultValue string `json:"defaultValue"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
return properties.LoadProperty(paramsStruct.Name, paramsStruct.DefaultValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setSwitchAddress(nSwitchAddress string) error {
|
||||||
|
err := properties.SaveSwitchAddress(nSwitchAddress)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
switchAddress = nSwitchAddress
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getSwitchAddress() (string, error) {
|
||||||
|
return switchAddress, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func setProxy(value string) error {
|
||||||
|
err := properties.SaveProxy(value)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
changeProxyUrl(value)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getProxy() (string, error) {
|
||||||
|
return properties.LoadProxy()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setUsername(value string) error {
|
||||||
|
return properties.SaveUsername(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getUsername() (string, error) {
|
||||||
|
return properties.LoadUsername()
|
||||||
|
}
|
||||||
|
|
||||||
|
func setPassword(value string) error {
|
||||||
|
return properties.SavePassword(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPassword() (string, error) {
|
||||||
|
return properties.LoadPassword()
|
||||||
|
}
|
||||||
|
|
||||||
|
func preLogin() (string, error) {
|
||||||
|
token, _ := properties.LoadToken()
|
||||||
|
tokenTime, _ := properties.LoadTokenTime()
|
||||||
|
if token != "" && tokenTime > 0 {
|
||||||
|
if utils.Timestamp()-(1000*60*60*24) < tokenTime {
|
||||||
|
client.Token = token
|
||||||
|
return "true", nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := login()
|
||||||
|
if err == nil {
|
||||||
|
return "true", nil
|
||||||
|
}
|
||||||
|
return "false", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func login() error {
|
||||||
|
username, _ := properties.LoadUsername()
|
||||||
|
password, _ := properties.LoadPassword()
|
||||||
|
if password == "" || username == "" {
|
||||||
|
return errors.New(" 需要设定用户名和密码 ")
|
||||||
|
}
|
||||||
|
err := client.Login(username, password)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
properties.SaveToken(client.Token)
|
||||||
|
properties.SaveTokenTime(utils.Timestamp())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func register(params string) error {
|
||||||
|
var dto source.RegisterDto
|
||||||
|
err := json.Unmarshal([]byte(params), &dto)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return client.Register(dto)
|
||||||
|
}
|
||||||
|
|
||||||
|
func clearToken() error {
|
||||||
|
properties.SaveTokenTime(0)
|
||||||
|
properties.SaveToken("")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func userProfile() (string, error) {
|
||||||
|
return serialize(client.UserProfile())
|
||||||
|
}
|
||||||
|
|
||||||
|
func punchIn() (string, error) {
|
||||||
|
return serialize(client.PunchIn())
|
||||||
|
}
|
||||||
|
|
||||||
|
func remoteImageData(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
FileServer string `json:"fileServer"`
|
||||||
|
Path string `json:"path"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
fileServer := paramsStruct.FileServer
|
||||||
|
path := paramsStruct.Path
|
||||||
|
lock := utils.HashLock(fmt.Sprintf("%s$%s", fileServer, path))
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
cache := comic_center.FindRemoteImage(fileServer, path)
|
||||||
|
if cache == nil {
|
||||||
|
buff, img, format, err := decodeFromUrl(fileServer, path)
|
||||||
|
if err != nil {
|
||||||
|
println(fmt.Sprintf("decode error : %s/static/%s %s", fileServer, path, err.Error()))
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
local :=
|
||||||
|
fmt.Sprintf("%x",
|
||||||
|
md5.Sum([]byte(fmt.Sprintf("%s$%s", fileServer, path))),
|
||||||
|
)
|
||||||
|
real := remotePath(local)
|
||||||
|
err = ioutil.WriteFile(
|
||||||
|
real,
|
||||||
|
buff, os.FileMode(0600),
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
remote := comic_center.RemoteImage{
|
||||||
|
FileServer: fileServer,
|
||||||
|
Path: path,
|
||||||
|
FileSize: int64(len(buff)),
|
||||||
|
Format: format,
|
||||||
|
Width: int32(img.Bounds().Dx()),
|
||||||
|
Height: int32(img.Bounds().Dy()),
|
||||||
|
LocalPath: local,
|
||||||
|
}
|
||||||
|
err = comic_center.SaveRemoteImage(&remote)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
cache = &remote
|
||||||
|
}
|
||||||
|
display := DisplayImageData{
|
||||||
|
FileSize: cache.FileSize,
|
||||||
|
Format: cache.Format,
|
||||||
|
Width: cache.Width,
|
||||||
|
Height: cache.Height,
|
||||||
|
FinalPath: remotePath(cache.LocalPath),
|
||||||
|
}
|
||||||
|
return serialize(&display, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadImagePath(path string) (string, error) {
|
||||||
|
return downloadPath(path), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func createDownload(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Comic comic_center.ComicDownload `json:"comic"`
|
||||||
|
EpList []comic_center.ComicDownloadEp `json:"epList"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comic := paramsStruct.Comic
|
||||||
|
epList := paramsStruct.EpList
|
||||||
|
if comic.Title == "" || len(epList) == 0 {
|
||||||
|
return errors.New("params error")
|
||||||
|
}
|
||||||
|
err := comic_center.CreateDownload(&comic, &epList)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// 创建文件夹
|
||||||
|
utils.Mkdir(downloadPath(comic.ID))
|
||||||
|
// 复制图标
|
||||||
|
downloadComicLogo(&comic)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadComicLogo(comic *comic_center.ComicDownload) {
|
||||||
|
lock := utils.HashLock(fmt.Sprintf("%s$%s", comic.ThumbFileServer, comic.ThumbPath))
|
||||||
|
lock.Lock()
|
||||||
|
defer lock.Unlock()
|
||||||
|
buff, image, format, err := decodeFromCache(comic.ThumbFileServer, comic.ThumbPath)
|
||||||
|
if err != nil {
|
||||||
|
buff, image, format, err = decodeFromUrl(comic.ThumbFileServer, comic.ThumbPath)
|
||||||
|
}
|
||||||
|
if err == nil {
|
||||||
|
comicLogoPath := path2.Join(comic.ID, "logo")
|
||||||
|
ioutil.WriteFile(downloadPath(comicLogoPath), buff, const_value.CreateFileMode)
|
||||||
|
comic_center.UpdateDownloadLogo(
|
||||||
|
comic.ID,
|
||||||
|
int64(len(buff)),
|
||||||
|
format,
|
||||||
|
int32(image.Bounds().Dx()),
|
||||||
|
int32(image.Bounds().Dy()),
|
||||||
|
comicLogoPath,
|
||||||
|
)
|
||||||
|
comic.ThumbFileSize = int64(len(buff))
|
||||||
|
comic.ThumbFormat = format
|
||||||
|
comic.ThumbWidth = int32(image.Bounds().Dx())
|
||||||
|
comic.ThumbHeight = int32(image.Bounds().Dy())
|
||||||
|
comic.ThumbLocalPath = comicLogoPath
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
println(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func addDownload(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Comic comic_center.ComicDownload `json:"comic"`
|
||||||
|
EpList []comic_center.ComicDownloadEp `json:"epList"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
comic := paramsStruct.Comic
|
||||||
|
epList := paramsStruct.EpList
|
||||||
|
if comic.Title == "" || len(epList) == 0 {
|
||||||
|
return errors.New("params error")
|
||||||
|
}
|
||||||
|
return comic_center.AddDownload(&comic, &epList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deleteDownloadComic(comicId string) error {
|
||||||
|
err := comic_center.Deleting(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
downloadRestart = true
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadDownloadComic(comicId string) (string, error) {
|
||||||
|
download, err := comic_center.FindComicDownloadById(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if download == nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
comic_center.ViewComic(comicId) // VIEW
|
||||||
|
return serialize(download, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func allDownloads() (string, error) {
|
||||||
|
return serialize(comic_center.AllDownloads())
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadEpList(comicId string) (string, error) {
|
||||||
|
return serialize(comic_center.ListDownloadEpByComicId(comicId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func viewLogPage(params string) (string, error) {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Offset int `json:"offset"`
|
||||||
|
Limit int `json:"limit"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
return serialize(comic_center.ViewLogPage(paramsStruct.Offset, paramsStruct.Limit))
|
||||||
|
}
|
||||||
|
|
||||||
|
func downloadPicturesByEpId(epId string) (string, error) {
|
||||||
|
return serialize(comic_center.ListDownloadPictureByEpId(epId))
|
||||||
|
}
|
||||||
|
|
||||||
|
func getDownloadRunning() bool {
|
||||||
|
return downloadRunning
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDownloadRunning(status bool) {
|
||||||
|
downloadRunning = status
|
||||||
|
}
|
||||||
|
|
||||||
|
func clean() error {
|
||||||
|
var err error
|
||||||
|
notifyExport("清理网络缓存")
|
||||||
|
err = network_cache.RemoveAll()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
notifyExport("清理图片缓存")
|
||||||
|
err = comic_center.RemoveAllRemoteImage()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
notifyExport("清理图片文件")
|
||||||
|
os.RemoveAll(remoteDir)
|
||||||
|
utils.Mkdir(remoteDir)
|
||||||
|
notifyExport("清理结束")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func autoClean(expire int64) error {
|
||||||
|
now := time.Now()
|
||||||
|
earliest := now.Add(time.Second * time.Duration(0-expire))
|
||||||
|
err := network_cache.RemoveEarliest(earliest)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pageSize := 10
|
||||||
|
for true {
|
||||||
|
images, err := comic_center.EarliestRemoteImage(earliest, pageSize)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if len(images) == 0 {
|
||||||
|
return comic_center.VACUUM()
|
||||||
|
}
|
||||||
|
// delete data & remove pic
|
||||||
|
err = comic_center.DeleteRemoteImages(images)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for i := 0; i < len(images); i++ {
|
||||||
|
err = os.Remove(remotePath(images[i].LocalPath))
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func storeViewEp(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
ComicId string `json:"comicId"`
|
||||||
|
EpOrder int `json:"epOrder"`
|
||||||
|
EpTitle string `json:"epTitle"`
|
||||||
|
PictureRank int `json:"pictureRank"`
|
||||||
|
}
|
||||||
|
json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
return comic_center.ViewEpAndPicture(
|
||||||
|
paramsStruct.ComicId,
|
||||||
|
paramsStruct.EpOrder,
|
||||||
|
paramsStruct.EpTitle,
|
||||||
|
paramsStruct.PictureRank,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
func loadView(comicId string) (string, error) {
|
||||||
|
view, err := comic_center.LoadViewLog(comicId)
|
||||||
|
if err != nil {
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
if view != nil {
|
||||||
|
b, err := json.Marshal(view)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}
|
||||||
|
return "", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func convertImageToJPEG100(params string) error {
|
||||||
|
var paramsStruct struct {
|
||||||
|
Path string `json:"path"`
|
||||||
|
Dir string `json:"dir"`
|
||||||
|
}
|
||||||
|
err := json.Unmarshal([]byte(params), ¶msStruct)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
_, i, _, err := decodeFromFile(paramsStruct.Path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
to := path2.Join(paramsStruct.Dir, path2.Base(paramsStruct.Path)+".jpg")
|
||||||
|
stream, err := os.Create(to)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer stream.Close()
|
||||||
|
return jpeg.Encode(stream, i, &jpeg.Options{Quality: 100})
|
||||||
|
}
|
||||||
|
|
||||||
|
func FlatInvoke(method string, params string) (string, error) {
|
||||||
|
switch method {
|
||||||
|
case "saveProperty":
|
||||||
|
return "", saveProperty(params)
|
||||||
|
case "loadProperty":
|
||||||
|
return loadProperty(params)
|
||||||
|
case "setSwitchAddress":
|
||||||
|
return "", setSwitchAddress(params)
|
||||||
|
case "getSwitchAddress":
|
||||||
|
return getSwitchAddress()
|
||||||
|
case "setProxy":
|
||||||
|
return "", setProxy(params)
|
||||||
|
case "getProxy":
|
||||||
|
return getProxy()
|
||||||
|
case "setUsername":
|
||||||
|
return "", setUsername(params)
|
||||||
|
case "setPassword":
|
||||||
|
return "", setPassword(params)
|
||||||
|
case "getUsername":
|
||||||
|
return getUsername()
|
||||||
|
case "getPassword":
|
||||||
|
return getPassword()
|
||||||
|
case "preLogin":
|
||||||
|
return preLogin()
|
||||||
|
case "login":
|
||||||
|
return "", login()
|
||||||
|
case "register":
|
||||||
|
return "", register(params)
|
||||||
|
case "clearToken":
|
||||||
|
return "", clearToken()
|
||||||
|
case "userProfile":
|
||||||
|
return userProfile()
|
||||||
|
case "punchIn":
|
||||||
|
return punchIn()
|
||||||
|
case "categories":
|
||||||
|
return categories()
|
||||||
|
case "comics":
|
||||||
|
return comics(params)
|
||||||
|
case "searchComics":
|
||||||
|
return searchComics(params)
|
||||||
|
case "randomComics":
|
||||||
|
return randomComics()
|
||||||
|
case "leaderboard":
|
||||||
|
return leaderboard(params)
|
||||||
|
case "comicInfo":
|
||||||
|
return comicInfo(params)
|
||||||
|
case "comicEpPage":
|
||||||
|
return epPage(params)
|
||||||
|
case "comicPicturePageWithQuality":
|
||||||
|
return comicPicturePageWithQuality(params)
|
||||||
|
case "switchLike":
|
||||||
|
return switchLike(params)
|
||||||
|
case "switchFavourite":
|
||||||
|
return switchFavourite(params)
|
||||||
|
case "favouriteComics":
|
||||||
|
return favouriteComics(params)
|
||||||
|
case "recommendation":
|
||||||
|
return recommendation(params)
|
||||||
|
case "comments":
|
||||||
|
return comments(params)
|
||||||
|
case "commentChildren":
|
||||||
|
return commentChildren(params)
|
||||||
|
case "myComments":
|
||||||
|
return myComments(params)
|
||||||
|
case "postComment":
|
||||||
|
return postComment(params)
|
||||||
|
case "postChildComment":
|
||||||
|
return postChildComment(params)
|
||||||
|
case "game":
|
||||||
|
return game(params)
|
||||||
|
case "games":
|
||||||
|
return games(params)
|
||||||
|
case "viewLogPage":
|
||||||
|
return viewLogPage(params)
|
||||||
|
case "clearAllViewLog":
|
||||||
|
comic_center.ClearAllViewLog()
|
||||||
|
return "", nil
|
||||||
|
case "deleteViewLog":
|
||||||
|
comic_center.DeleteViewLog(params)
|
||||||
|
return "", nil
|
||||||
|
case "clean":
|
||||||
|
return "", clean()
|
||||||
|
case "autoClean":
|
||||||
|
expire, err := strconv.ParseInt(params, 10, 64)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return "", autoClean(expire)
|
||||||
|
case "storeViewEp":
|
||||||
|
return "", storeViewEp(params)
|
||||||
|
case "loadView":
|
||||||
|
return loadView(params)
|
||||||
|
case "downloadRunning":
|
||||||
|
return strconv.FormatBool(getDownloadRunning()), nil
|
||||||
|
case "setDownloadRunning":
|
||||||
|
b, e := strconv.ParseBool(params)
|
||||||
|
if e != nil {
|
||||||
|
setDownloadRunning(b)
|
||||||
|
}
|
||||||
|
return "", e
|
||||||
|
case "createDownload":
|
||||||
|
return "", createDownload(params)
|
||||||
|
case "addDownload":
|
||||||
|
return "", addDownload(params)
|
||||||
|
case "loadDownloadComic":
|
||||||
|
return loadDownloadComic(params)
|
||||||
|
case "allDownloads":
|
||||||
|
return allDownloads()
|
||||||
|
case "deleteDownloadComic":
|
||||||
|
return "", deleteDownloadComic(params)
|
||||||
|
case "downloadEpList":
|
||||||
|
return downloadEpList(params)
|
||||||
|
case "downloadPicturesByEpId":
|
||||||
|
return downloadPicturesByEpId(params)
|
||||||
|
case "resetAllDownloads":
|
||||||
|
return "", comic_center.ResetAll()
|
||||||
|
case "exportComicDownload":
|
||||||
|
return "", exportComicDownload(params)
|
||||||
|
case "exportComicDownloadToJPG":
|
||||||
|
return "", exportComicDownloadToJPG(params)
|
||||||
|
case "exportComicUsingSocket":
|
||||||
|
i, e := exportComicUsingSocket(params)
|
||||||
|
return fmt.Sprintf("%d", i), e
|
||||||
|
case "exportComicUsingSocketExit":
|
||||||
|
return "", exportComicUsingSocketExit()
|
||||||
|
case "importComicDownload":
|
||||||
|
return "", importComicDownload(params)
|
||||||
|
case "importComicDownloadUsingSocket":
|
||||||
|
return "", importComicDownloadUsingSocket(params)
|
||||||
|
case "remoteImageData":
|
||||||
|
return remoteImageData(params)
|
||||||
|
case "clientIpSet":
|
||||||
|
return clientIpSet()
|
||||||
|
case "downloadImagePath":
|
||||||
|
return downloadImagePath(params)
|
||||||
|
case "downloadGame":
|
||||||
|
return downloadGame(params)
|
||||||
|
case "convertImageToJPEG100":
|
||||||
|
return "", convertImageToJPEG100(params)
|
||||||
|
}
|
||||||
|
return "", errors.New("method not found : " + method)
|
||||||
|
}
|
|
@ -0,0 +1,593 @@
|
||||||
|
package comic_center
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"path"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mutex = sync.Mutex{}
|
||||||
|
var db *gorm.DB
|
||||||
|
|
||||||
|
func InitDBConnect(databaseDir string) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var err error
|
||||||
|
db, err = gorm.Open(sqlite.Open(path.Join(databaseDir, "comic_center.db")), const_value.GormConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to connect database")
|
||||||
|
}
|
||||||
|
db.AutoMigrate(&Category{})
|
||||||
|
db.AutoMigrate(&ComicView{})
|
||||||
|
db.AutoMigrate(&RemoteImage{})
|
||||||
|
db.AutoMigrate(&ComicDownload{})
|
||||||
|
db.AutoMigrate(&ComicDownloadEp{})
|
||||||
|
db.AutoMigrate(&ComicDownloadPicture{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func Transaction(t func(tx *gorm.DB) error) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpSetCategories(categories *[]Category) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
var in []string
|
||||||
|
for _, c := range *categories {
|
||||||
|
if c.ID == "" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
in = append(in, c.ID)
|
||||||
|
err := tx.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "id"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{
|
||||||
|
"updated_at",
|
||||||
|
"title",
|
||||||
|
"description",
|
||||||
|
"is_web",
|
||||||
|
"active",
|
||||||
|
"link",
|
||||||
|
"thumb_original_name",
|
||||||
|
"thumb_file_server",
|
||||||
|
"thumb_path",
|
||||||
|
}),
|
||||||
|
}).Create(&c).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := tx.Unscoped().Model(&Category{}).Where(" id in ?", in).Update("deleted_at", gorm.DeletedAt{
|
||||||
|
Valid: false,
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Unscoped().Model(&Category{}).Where(" id not in ?", in).Update("deleted_at", gorm.DeletedAt{
|
||||||
|
Time: time.Now(),
|
||||||
|
Valid: true,
|
||||||
|
}).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func NoLockActionViewComicUpdateInfoDB(view *ComicView, db *gorm.DB) error {
|
||||||
|
view.LastViewTime = time.Now()
|
||||||
|
return db.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "id"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{
|
||||||
|
"created_at",
|
||||||
|
"updated_at",
|
||||||
|
"title",
|
||||||
|
"author",
|
||||||
|
"pages_count",
|
||||||
|
"eps_count",
|
||||||
|
"finished",
|
||||||
|
"categories",
|
||||||
|
"thumb_original_name",
|
||||||
|
"thumb_file_server",
|
||||||
|
"thumb_path",
|
||||||
|
"likes_count",
|
||||||
|
"description",
|
||||||
|
"chinese_team",
|
||||||
|
"tags",
|
||||||
|
"allow_download",
|
||||||
|
"views_count",
|
||||||
|
"is_favourite",
|
||||||
|
"is_liked",
|
||||||
|
"comments_count",
|
||||||
|
"last_view_time",
|
||||||
|
}),
|
||||||
|
}).Create(view).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ViewComicUpdateInfo(view *ComicView) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return NoLockActionViewComicUpdateInfoDB(view, db)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ViewComic(comicId string) error {
|
||||||
|
return db.Model(&ComicView{}).Where(
|
||||||
|
"id = ?", comicId,
|
||||||
|
).Update(
|
||||||
|
"last_view_time",
|
||||||
|
time.Now(),
|
||||||
|
).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ViewComicUpdateFavourite(comicId string, favourite bool) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicView{}).Where(
|
||||||
|
"id = ?", comicId,
|
||||||
|
).Update(
|
||||||
|
"is_favourite",
|
||||||
|
favourite,
|
||||||
|
).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ViewComicUpdateLike(comicId string, like bool) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicView{}).Where(
|
||||||
|
"id = ?", comicId,
|
||||||
|
).Update(
|
||||||
|
"is_like",
|
||||||
|
like,
|
||||||
|
).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func ViewEpAndPicture(comicId string, epOrder int, epTitle string, pictureRank int) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicView{}).Where("id", comicId).Updates(
|
||||||
|
map[string]interface{}{
|
||||||
|
"last_view_time": time.Now(),
|
||||||
|
"last_view_ep_order": epOrder,
|
||||||
|
"last_view_ep_title": epTitle,
|
||||||
|
"last_view_picture_rank": pictureRank,
|
||||||
|
},
|
||||||
|
).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadViewLog(comicId string) (*ComicView, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var view ComicView
|
||||||
|
err := db.First(&view, "id = ?", comicId).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &view, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindRemoteImage(fileServer string, path string) *RemoteImage {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var remoteImage RemoteImage
|
||||||
|
err := db.First(&remoteImage, "file_server = ? AND path = ?", fileServer, path).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return &remoteImage
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveRemoteImage(remote *RemoteImage) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "file_server"}, {Name: "path"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{
|
||||||
|
"updated_at",
|
||||||
|
"file_size",
|
||||||
|
"format",
|
||||||
|
"width",
|
||||||
|
"height",
|
||||||
|
"local_path",
|
||||||
|
}),
|
||||||
|
}).Create(remote).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateDownload(comic *ComicDownload, epList *[]ComicDownloadEp) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
comic.SelectedEpCount = int32(len(*epList))
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Create(comic).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ep := range *epList {
|
||||||
|
err := tx.Create(&ep).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func AddDownload(comic *ComicDownload, epList *[]ComicDownloadEp) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Model(comic).Where("id = ?", comic.ID).Updates(map[string]interface{}{
|
||||||
|
"created_at": comic.CreatedAt,
|
||||||
|
"updated_at": comic.UpdatedAt,
|
||||||
|
"title": comic.Title,
|
||||||
|
"author": comic.Author,
|
||||||
|
"pages_count": comic.PagesCount,
|
||||||
|
"eps_count": comic.EpsCount,
|
||||||
|
"finished": comic.Finished,
|
||||||
|
"categories": comic.Categories,
|
||||||
|
"thumb_original_name": comic.ThumbOriginalName,
|
||||||
|
"thumb_file_server": comic.ThumbFileServer,
|
||||||
|
"thumb_path": comic.ThumbPath,
|
||||||
|
"description": comic.Description,
|
||||||
|
"chinese_team": comic.ChineseTeam,
|
||||||
|
"tags": comic.Tags,
|
||||||
|
"download_finished": false, // restart
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Exec(
|
||||||
|
"UPDATE comic_downloads SET eps_count = selected_ep_count + ? WHERE id = ?",
|
||||||
|
len(*epList), comic.ID,
|
||||||
|
).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
for _, ep := range *epList {
|
||||||
|
err := tx.Create(&ep).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func UpdateDownloadLogo(comicId string, fileSize int64, format string, width int32, height int32, localPath string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Updates(map[string]interface{}{
|
||||||
|
"thumb_file_size": fileSize,
|
||||||
|
"thumb_format": format,
|
||||||
|
"thumb_width": width,
|
||||||
|
"thumb_height": height,
|
||||||
|
"thumb_local_path": localPath,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func FindComicDownloadById(comicId string) (*ComicDownload, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var download ComicDownload
|
||||||
|
err := db.First(&download, "id = ?", comicId).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &download, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListDownloadEpByComicId(comicId string) ([]ComicDownloadEp, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var epList []ComicDownloadEp
|
||||||
|
err := db.Where("comic_id = ?", comicId).Order("ep_order ASC").Find(&epList).Error
|
||||||
|
return epList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListDownloadPictureByEpId(epId string) ([]ComicDownloadPicture, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var pictureList []ComicDownloadPicture
|
||||||
|
err := db.Where("ep_id = ?", epId).Order("rank_in_ep ASC").Find(&pictureList).Error
|
||||||
|
return pictureList, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func AllDownloads() (*[]ComicDownload, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var downloads []ComicDownload
|
||||||
|
err := db.Table("comic_downloads").
|
||||||
|
Joins("LEFT JOIN comic_views ON comic_views.id = comic_downloads.id").
|
||||||
|
Select("comic_downloads.*").
|
||||||
|
Order("comic_views.last_view_time DESC").
|
||||||
|
Scan(&downloads).Error
|
||||||
|
// err := db.Find(&downloads).Error
|
||||||
|
return &downloads, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFirstNeedDownload() (*ComicDownload, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var download ComicDownload
|
||||||
|
err := db.First(&download, "download_failed = 0 AND pause = 0 AND deleting = 0 AND download_finished = 0").Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &download, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFirstNeedDownloadEp(comicId string) (*ComicDownloadEp, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var ep ComicDownloadEp
|
||||||
|
err := db.First(
|
||||||
|
&ep,
|
||||||
|
" comic_id = ? AND download_failed = 0 AND download_finished = 0 AND fetched_pictures = 1",
|
||||||
|
comicId,
|
||||||
|
).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &ep, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadFirstNeedDownloadPicture(epId string) (*ComicDownloadPicture, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var picture ComicDownloadPicture
|
||||||
|
err := db.First(
|
||||||
|
&picture,
|
||||||
|
"ep_id = ? AND download_failed = 0 AND download_finished = 0",
|
||||||
|
epId,
|
||||||
|
).Error
|
||||||
|
if err != nil {
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return &picture, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func FetchPictures(comicId string, epId string, list *[]ComicDownloadPicture) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
var rankInEp int32
|
||||||
|
for _, picture := range *list {
|
||||||
|
rankInEp = rankInEp + 1
|
||||||
|
picture.RankInEp = rankInEp
|
||||||
|
err := tx.Create(&picture).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
err := tx.Model(&ComicDownloadEp{}).Where("id = ?", epId).Updates(map[string]interface{}{
|
||||||
|
"fetched_pictures": true,
|
||||||
|
"selected_picture_count": len(*list),
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Exec(
|
||||||
|
"UPDATE comic_downloads SET selected_picture_count = selected_picture_count + ? WHERE id = ?",
|
||||||
|
len(*list), comicId,
|
||||||
|
).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownloadFailed(comicId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Update("download_failed", true).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func DownloadSuccess(comicId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Updates(map[string]interface{}{
|
||||||
|
"download_finished": true,
|
||||||
|
"download_finished_time": time.Now(),
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func EpFailed(epId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicDownloadEp{}).Where("id = ?", epId).Update("download_failed", true).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func EpSuccess(comicId string, epId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Model(&ComicDownloadEp{}).Where("id = ?", epId).Updates(map[string]interface{}{
|
||||||
|
"download_finished": true,
|
||||||
|
"download_finished_time": time.Now(),
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Exec(
|
||||||
|
"UPDATE comic_downloads SET download_ep_count = download_ep_count + 1 WHERE id = ?",
|
||||||
|
comicId,
|
||||||
|
).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func PictureFailed(pictureId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicDownloadPicture{}).Where("id = ?", pictureId).Update("download_failed", true).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func PictureSuccess(
|
||||||
|
comicId string, epId string, pictureId string,
|
||||||
|
fileSize int64, format string, width int32, height int32, localPath string,
|
||||||
|
) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Model(&ComicDownloadPicture{}).Where("id = ?", pictureId).Updates(map[string]interface{}{
|
||||||
|
"file_size": fileSize,
|
||||||
|
"format": format,
|
||||||
|
"width": width,
|
||||||
|
"height": height,
|
||||||
|
"local_path": localPath,
|
||||||
|
"download_finished": true,
|
||||||
|
"download_finished_time": time.Now(),
|
||||||
|
}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Exec(
|
||||||
|
"UPDATE comic_download_eps SET download_picture_count = download_picture_count + 1 WHERE id = ?",
|
||||||
|
epId,
|
||||||
|
).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tx.Exec(
|
||||||
|
"UPDATE comic_downloads SET download_picture_count = download_picture_count + 1 WHERE id = ?",
|
||||||
|
comicId,
|
||||||
|
).Error
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ResetAll() error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Model(&ComicDownload{}).Where("1 = 1").
|
||||||
|
Update("download_failed", false).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Model(&ComicDownloadEp{}).Where("1 = 1").
|
||||||
|
Update("download_failed", false).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Model(&ComicDownloadPicture{}).Where("1 = 1").
|
||||||
|
Update("download_failed", false).Error
|
||||||
|
return err
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func ViewLogPage(offset int, limit int) (*[]ComicView, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var list []ComicView
|
||||||
|
err := db.Offset(offset).Limit(limit).Order("last_view_time DESC").Find(&list).Error
|
||||||
|
return &list, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func ClearAllViewLog() {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
db.Unscoped().Where("1 = 1").Delete(&ComicView{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteViewLog(id string) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
db.Unscoped().Where("id = ?", id).Delete(&ComicView{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeletingComic() (*ComicDownload, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var download ComicDownload
|
||||||
|
err := db.First(&download, "deleting = 1").Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
return &download, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func TrueDelete(comicId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
err := db.Transaction(func(tx *gorm.DB) error {
|
||||||
|
err := tx.Unscoped().Delete(&ComicDownload{}, "id = ?", comicId).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Unscoped().Delete(&ComicDownloadEp{}, "comic_id = ?", comicId).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
err = tx.Unscoped().Delete(&ComicDownloadPicture{}, "comic_id = ?", comicId).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.Raw("VACUUM").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func Deleting(comicId string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Model(&ComicDownload{}).Where("id = ?", comicId).Updates(map[string]interface{}{
|
||||||
|
"deleting": true,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveAllRemoteImage() error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
err := db.Unscoped().Delete(&RemoteImage{}, "1 = 1").Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.Raw("VACUUM").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func EarliestRemoteImage(earliest time.Time, pageSize int) ([]RemoteImage, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var images []RemoteImage
|
||||||
|
err := db.Where("strftime('%s',updated_at) < strftime('%s',?)", earliest).
|
||||||
|
Order("updated_at").Limit(pageSize).Find(&images).Error
|
||||||
|
return images, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteRemoteImages(images []RemoteImage) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
if len(images) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
ids := make([]uint, len(images))
|
||||||
|
for i := 0; i < len(images); i++ {
|
||||||
|
ids[i] = images[i].ID
|
||||||
|
}
|
||||||
|
return db.Unscoped().Model(&RemoteImage{}).Delete("id in ?", ids).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func VACUUM() error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Raw("VACUUM").Error
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package comic_center
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Category struct {
|
||||||
|
ID string `gorm:"primarykey"`
|
||||||
|
CreatedAt time.Time
|
||||||
|
UpdatedAt time.Time
|
||||||
|
DeletedAt gorm.DeletedAt `gorm:"index"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
IsWeb bool `json:"isWeb"`
|
||||||
|
Active bool `json:"active"`
|
||||||
|
Link string `json:"link"`
|
||||||
|
ThumbOriginalName string
|
||||||
|
ThumbFileServer string
|
||||||
|
ThumbPath string
|
||||||
|
}
|
||||||
|
|
||||||
|
type RemoteImage struct {
|
||||||
|
gorm.Model
|
||||||
|
FileServer string `gorm:"index:uk_fp,unique" json:"fileServer"`
|
||||||
|
Path string `gorm:"index:uk_fp,unique" json:"path"`
|
||||||
|
FileSize int64 `json:"fileSize"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
Width int32 `json:"width"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
LocalPath string `json:"localPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicSimple struct {
|
||||||
|
ID string `gorm:"primarykey" json:"id"`
|
||||||
|
CreatedAt time.Time `json:"createdAt"`
|
||||||
|
UpdatedAt time.Time `json:"updatedAt"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
Author string `json:"author"`
|
||||||
|
PagesCount int32 `json:"pagesCount"`
|
||||||
|
EpsCount int32 `json:"epsCount"`
|
||||||
|
Finished bool `json:"finished"`
|
||||||
|
Categories string `json:"categories"`
|
||||||
|
ThumbOriginalName string `json:"thumbOriginalName"`
|
||||||
|
ThumbFileServer string `json:"thumbFileServer"`
|
||||||
|
ThumbPath string `json:"thumbPath"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicInfo struct {
|
||||||
|
ComicSimple
|
||||||
|
LikesCount int32 `json:"likesCount"`
|
||||||
|
Description string `json:"description"`
|
||||||
|
ChineseTeam string `json:"chineseTeam"`
|
||||||
|
Tags string `json:"tags"`
|
||||||
|
AllowDownload bool `json:"allowDownload"`
|
||||||
|
ViewsCount int32 `json:"viewsCount"`
|
||||||
|
IsFavourite bool `json:"isFavourite"`
|
||||||
|
IsLiked bool `json:"isLiked"`
|
||||||
|
CommentsCount int32 `json:"commentsCount"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicView struct {
|
||||||
|
ComicInfo
|
||||||
|
LastViewTime time.Time `json:"lastViewTime"`
|
||||||
|
LastViewEpOrder int32 `json:"lastViewEpOrder"`
|
||||||
|
LastViewEpTitle string `json:"lastViewEpTitle"`
|
||||||
|
LastViewPictureRank int32 `json:"lastViewPictureRank"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicDownload struct {
|
||||||
|
ComicSimple
|
||||||
|
Description string `json:"description"`
|
||||||
|
ChineseTeam string `json:"chineseTeam"`
|
||||||
|
Tags string `json:"tags"`
|
||||||
|
SelectedEpCount int32 `json:"selectedEpCount"`
|
||||||
|
SelectedPictureCount int32 `json:"selectedPictureCount"`
|
||||||
|
DownloadEpCount int32 `json:"downloadEpCount"`
|
||||||
|
DownloadPictureCount int32 `json:"downloadPictureCount"`
|
||||||
|
DownloadFinished bool `json:"downloadFinished"`
|
||||||
|
DownloadFinishedTime time.Time `json:"downloadFinishedTime"`
|
||||||
|
DownloadFailed bool `json:"downloadFailed"`
|
||||||
|
Deleting bool `json:"deleting"`
|
||||||
|
ThumbFileSize int64 `json:"thumbFileSize"`
|
||||||
|
ThumbFormat string `json:"thumbFormat"`
|
||||||
|
ThumbWidth int32 `json:"thumbWidth"`
|
||||||
|
ThumbHeight int32 `json:"thumbHeight"`
|
||||||
|
ThumbLocalPath string `json:"thumbLocalPath"`
|
||||||
|
Pause bool `json:"pause"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicDownloadEp struct {
|
||||||
|
ComicId string `gorm:"index:idx_comic_id" json:"comicId"`
|
||||||
|
ID string `gorm:"primarykey" json:"id"`
|
||||||
|
UpdatedAt time.Time `json:"updated_at"`
|
||||||
|
EpOrder int32 `json:"epOrder"`
|
||||||
|
Title string `json:"title"`
|
||||||
|
FetchedPictures bool `json:"fetchedPictures"`
|
||||||
|
SelectedPictureCount int32 `json:"selectedPictureCount"`
|
||||||
|
DownloadPictureCount int32 `json:"downloadPictureCount"`
|
||||||
|
DownloadFinished bool `json:"downloadFinish"`
|
||||||
|
DownloadFinishedTime time.Time `json:"downloadFinishTime"`
|
||||||
|
DownloadFailed bool `json:"downloadFailed"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ComicDownloadPicture struct {
|
||||||
|
ID string `gorm:"primarykey" json:"id"`
|
||||||
|
ComicId string `gorm:"index:idx_comic_id" json:"comicId"`
|
||||||
|
EpId string `gorm:"index:idx_ep_id" json:"epId"`
|
||||||
|
EpOrder int32 `gorm:"index:idx_ep_order" json:"epOrder"`
|
||||||
|
RankInEp int32 `json:"rankInEp"`
|
||||||
|
DownloadFinished bool `json:"downloadFinish"`
|
||||||
|
DownloadFinishedTime time.Time `json:"downloadFinishTime"`
|
||||||
|
DownloadFailed bool `json:"downloadFailed"`
|
||||||
|
OriginalName string
|
||||||
|
FileServer string `gorm:"index:idx_fp,priority:1" json:"fileServer"`
|
||||||
|
Path string `gorm:"index:idx_fp,priority:2" json:"path"`
|
||||||
|
FileSize int64 `json:"fileSize"`
|
||||||
|
Format string `json:"format"`
|
||||||
|
Width int32 `json:"width"`
|
||||||
|
Height int32 `json:"height"`
|
||||||
|
LocalPath string `json:"localPath"`
|
||||||
|
}
|
|
@ -0,0 +1,99 @@
|
||||||
|
package network_cache
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"path"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mutex = sync.Mutex{}
|
||||||
|
var db *gorm.DB
|
||||||
|
|
||||||
|
type NetworkCache struct {
|
||||||
|
gorm.Model
|
||||||
|
K string `gorm:"index:uk_k,unique"`
|
||||||
|
V string
|
||||||
|
}
|
||||||
|
|
||||||
|
func InitDBConnect(databaseDir string) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var err error
|
||||||
|
db, err = gorm.Open(sqlite.Open(path.Join(databaseDir, "network_cache.db")), const_value.GormConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to connect database")
|
||||||
|
}
|
||||||
|
db.AutoMigrate(&NetworkCache{})
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadCache(key string, expire time.Duration) string {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var cache NetworkCache
|
||||||
|
err := db.First(&cache, "k = ? AND updated_at > ?", key, time.Now().Add(expire*-1)).Error
|
||||||
|
if err == nil {
|
||||||
|
return cache.V
|
||||||
|
}
|
||||||
|
if gorm.ErrRecordNotFound == err {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
panic(errors.New("?"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveCache(key string, value string) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
db.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "k"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{"created_at", "updated_at", "v"}),
|
||||||
|
}).Create(&NetworkCache{
|
||||||
|
K: key,
|
||||||
|
V: value,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveCache(key string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
err := db.Unscoped().Delete(&NetworkCache{}, "k = ?", key).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveCaches(like string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
err := db.Unscoped().Delete(&NetworkCache{}, "k LIKE ?", like).Error
|
||||||
|
if err == gorm.ErrRecordNotFound {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveAll() error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
err := db.Unscoped().Delete(&NetworkCache{}, "1 = 1").Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.Raw("VACUUM").Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveEarliest(earliest time.Time) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
err := db.Unscoped().Where("strftime('%s',updated_at) < strftime('%s',?)", earliest).
|
||||||
|
Delete(&NetworkCache{}).Error
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return db.Raw("VACUUM").Error
|
||||||
|
}
|
|
@ -0,0 +1,122 @@
|
||||||
|
package properties
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"gorm.io/driver/sqlite"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
"gorm.io/gorm/clause"
|
||||||
|
"path"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
"strconv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var mutex = sync.Mutex{}
|
||||||
|
var db *gorm.DB
|
||||||
|
|
||||||
|
func InitDBConnect(databaseDir string) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var err error
|
||||||
|
db, err = gorm.Open(sqlite.Open(path.Join(databaseDir, "properties.db")), const_value.GormConfig)
|
||||||
|
if err != nil {
|
||||||
|
panic("failed to connect database")
|
||||||
|
}
|
||||||
|
db.AutoMigrate(&Property{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type Property struct {
|
||||||
|
gorm.Model
|
||||||
|
K string `gorm:"index:uk_k,unique"`
|
||||||
|
V string
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadProperty(name string, defaultValue string) (string, error) {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
var property Property
|
||||||
|
err := db.First(&property, "k", name).Error
|
||||||
|
if err == nil {
|
||||||
|
return property.V, nil
|
||||||
|
}
|
||||||
|
if gorm.ErrRecordNotFound == err {
|
||||||
|
return defaultValue, nil
|
||||||
|
}
|
||||||
|
panic(errors.New("?"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveProperty(name string, value string) error {
|
||||||
|
mutex.Lock()
|
||||||
|
defer mutex.Unlock()
|
||||||
|
return db.Clauses(clause.OnConflict{
|
||||||
|
Columns: []clause.Column{{Name: "k"}},
|
||||||
|
DoUpdates: clause.AssignmentColumns([]string{"created_at", "updated_at", "v"}),
|
||||||
|
}).Create(&Property{
|
||||||
|
K: name,
|
||||||
|
V: value,
|
||||||
|
}).Error
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadBoolProperty(name string, defaultValue bool) (bool, error) {
|
||||||
|
stringValue, err := LoadProperty(name, strconv.FormatBool(defaultValue))
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return strconv.ParseBool(stringValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveBoolProperty(name string, value bool) error {
|
||||||
|
return SaveProperty(name, strconv.FormatBool(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveSwitchAddress(value string) error {
|
||||||
|
return SaveProperty("switch_address", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadSwitchAddress() (string, error) {
|
||||||
|
return LoadProperty("switch_address", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveProxy(value string) error {
|
||||||
|
return SaveProperty("proxy", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadProxy() (string, error) {
|
||||||
|
return LoadProperty("proxy", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveUsername(value string) error {
|
||||||
|
return SaveProperty("username", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadUsername() (string, error) {
|
||||||
|
return LoadProperty("username", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SavePassword(value string) error {
|
||||||
|
return SaveProperty("password", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadPassword() (string, error) {
|
||||||
|
return LoadProperty("password", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveToken(value string) {
|
||||||
|
SaveProperty("token", value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadToken() (string, error) {
|
||||||
|
return LoadProperty("token", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func SaveTokenTime(value int64) {
|
||||||
|
SaveProperty("token_time", strconv.FormatInt(value, 10))
|
||||||
|
}
|
||||||
|
|
||||||
|
func LoadTokenTime() (int64, error) {
|
||||||
|
str, err := LoadProperty("token_time", "0")
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return strconv.ParseInt(str, 10, 64)
|
||||||
|
}
|
|
@ -0,0 +1,19 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"pgo/pikapi/const_value"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Mkdir(dir string) {
|
||||||
|
if _, err := os.Stat(dir); err != nil {
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err = os.MkdirAll(dir, const_value.CreateDirMode)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import (
|
||||||
|
"hash/fnv"
|
||||||
|
"sync"
|
||||||
|
)
|
||||||
|
|
||||||
|
var hashMutex []*sync.Mutex
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
for i := 0; i < 32; i++ {
|
||||||
|
hashMutex = append(hashMutex, &sync.Mutex{})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// HashLock Hash一样的图片不同时处理
|
||||||
|
func HashLock(key string) *sync.Mutex {
|
||||||
|
hash := fnv.New32()
|
||||||
|
hash.Write([]byte(key))
|
||||||
|
return hashMutex[int(hash.Sum32()%uint32(len(hashMutex)))]
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
package utils
|
||||||
|
|
||||||
|
import "time"
|
||||||
|
|
||||||
|
func Timestamp() int64 {
|
||||||
|
return time.Now().UnixNano() / int64(time.Millisecond)
|
||||||
|
}
|
After Width: | Height: | Size: 251 KiB |
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 114 KiB |
After Width: | Height: | Size: 84 KiB |
After Width: | Height: | Size: 200 KiB |
After Width: | Height: | Size: 269 KiB |
After Width: | Height: | Size: 330 KiB |
After Width: | Height: | Size: 172 KiB |
After Width: | Height: | Size: 380 KiB |
|
@ -0,0 +1,33 @@
|
||||||
|
*.mode1v3
|
||||||
|
*.mode2v3
|
||||||
|
*.moved-aside
|
||||||
|
*.pbxuser
|
||||||
|
*.perspectivev3
|
||||||
|
**/*sync/
|
||||||
|
.sconsign.dblite
|
||||||
|
.tags*
|
||||||
|
**/.vagrant/
|
||||||
|
**/DerivedData/
|
||||||
|
Icon?
|
||||||
|
**/Pods/
|
||||||
|
**/.symlinks/
|
||||||
|
profile
|
||||||
|
xcuserdata
|
||||||
|
**/.generated/
|
||||||
|
Flutter/App.framework
|
||||||
|
Flutter/Flutter.framework
|
||||||
|
Flutter/Flutter.podspec
|
||||||
|
Flutter/Generated.xcconfig
|
||||||
|
Flutter/ephemeral/
|
||||||
|
Flutter/app.flx
|
||||||
|
Flutter/app.zip
|
||||||
|
Flutter/flutter_assets/
|
||||||
|
Flutter/flutter_export_environment.sh
|
||||||
|
ServiceDefinitions.json
|
||||||
|
Runner/GeneratedPluginRegistrant.*
|
||||||
|
|
||||||
|
# Exceptions to above rules.
|
||||||
|
!default.mode1v3
|
||||||
|
!default.mode2v3
|
||||||
|
!default.pbxuser
|
||||||
|
!default.perspectivev3
|
|
@ -0,0 +1,26 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>CFBundleDevelopmentRegion</key>
|
||||||
|
<string>en</string>
|
||||||
|
<key>CFBundleExecutable</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundleIdentifier</key>
|
||||||
|
<string>io.flutter.flutter.app</string>
|
||||||
|
<key>CFBundleInfoDictionaryVersion</key>
|
||||||
|
<string>6.0</string>
|
||||||
|
<key>CFBundleName</key>
|
||||||
|
<string>App</string>
|
||||||
|
<key>CFBundlePackageType</key>
|
||||||
|
<string>FMWK</string>
|
||||||
|
<key>CFBundleShortVersionString</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>CFBundleSignature</key>
|
||||||
|
<string>????</string>
|
||||||
|
<key>CFBundleVersion</key>
|
||||||
|
<string>1.0</string>
|
||||||
|
<key>MinimumOSVersion</key>
|
||||||
|
<string>8.0</string>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
|
@ -0,0 +1,2 @@
|
||||||
|
#include? "Pods/Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"
|
||||||
|
#include "Generated.xcconfig"
|
|
@ -0,0 +1,41 @@
|
||||||
|
# Uncomment this line to define a global platform for your project
|
||||||
|
# platform :ios, '9.0'
|
||||||
|
|
||||||
|
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
|
||||||
|
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
|
||||||
|
|
||||||
|
project 'Runner', {
|
||||||
|
'Debug' => :debug,
|
||||||
|
'Profile' => :release,
|
||||||
|
'Release' => :release,
|
||||||
|
}
|
||||||
|
|
||||||
|
def flutter_root
|
||||||
|
generated_xcode_build_settings_path = File.expand_path(File.join('..', 'Flutter', 'Generated.xcconfig'), __FILE__)
|
||||||
|
unless File.exist?(generated_xcode_build_settings_path)
|
||||||
|
raise "#{generated_xcode_build_settings_path} must exist. If you're running pod install manually, make sure flutter pub get is executed first"
|
||||||
|
end
|
||||||
|
|
||||||
|
File.foreach(generated_xcode_build_settings_path) do |line|
|
||||||
|
matches = line.match(/FLUTTER_ROOT\=(.*)/)
|
||||||
|
return matches[1].strip if matches
|
||||||
|
end
|
||||||
|
raise "FLUTTER_ROOT not found in #{generated_xcode_build_settings_path}. Try deleting Generated.xcconfig, then run flutter pub get"
|
||||||
|
end
|
||||||
|
|
||||||
|
require File.expand_path(File.join('packages', 'flutter_tools', 'bin', 'podhelper'), flutter_root)
|
||||||
|
|
||||||
|
flutter_ios_podfile_setup
|
||||||
|
|
||||||
|
target 'Runner' do
|
||||||
|
use_frameworks!
|
||||||
|
use_modular_headers!
|
||||||
|
|
||||||
|
flutter_install_all_ios_pods File.dirname(File.realpath(__FILE__))
|
||||||
|
end
|
||||||
|
|
||||||
|
post_install do |installer|
|
||||||
|
installer.pods_project.targets.each do |target|
|
||||||
|
flutter_additional_ios_build_settings(target)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,28 @@
|
||||||
|
PODS:
|
||||||
|
- Flutter (1.0.0)
|
||||||
|
- "permission_handler (5.1.0+2)":
|
||||||
|
- Flutter
|
||||||
|
- url_launcher (0.0.1):
|
||||||
|
- Flutter
|
||||||
|
|
||||||
|
DEPENDENCIES:
|
||||||
|
- Flutter (from `Flutter`)
|
||||||
|
- permission_handler (from `.symlinks/plugins/permission_handler/ios`)
|
||||||
|
- url_launcher (from `.symlinks/plugins/url_launcher/ios`)
|
||||||
|
|
||||||
|
EXTERNAL SOURCES:
|
||||||
|
Flutter:
|
||||||
|
:path: Flutter
|
||||||
|
permission_handler:
|
||||||
|
:path: ".symlinks/plugins/permission_handler/ios"
|
||||||
|
url_launcher:
|
||||||
|
:path: ".symlinks/plugins/url_launcher/ios"
|
||||||
|
|
||||||
|
SPEC CHECKSUMS:
|
||||||
|
Flutter: 434fef37c0980e73bb6479ef766c45957d4b510c
|
||||||
|
permission_handler: ccb20a9fad0ee9b1314a52b70b76b473c5f8dab0
|
||||||
|
url_launcher: 6fef411d543ceb26efce54b05a0a40bfd74cbbef
|
||||||
|
|
||||||
|
PODFILE CHECKSUM: aafe91acc616949ddb318b77800a7f51bffa2a4c
|
||||||
|
|
||||||
|
COCOAPODS: 1.10.1
|
|
@ -0,0 +1,555 @@
|
||||||
|
// !$*UTF8*$!
|
||||||
|
{
|
||||||
|
archiveVersion = 1;
|
||||||
|
classes = {
|
||||||
|
};
|
||||||
|
objectVersion = 50;
|
||||||
|
objects = {
|
||||||
|
|
||||||
|
/* Begin PBXBuildFile section */
|
||||||
|
0E44DEFD92B805627806403C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 605DB0C59210B25A843453FD /* Pods_Runner.framework */; };
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 74858FAE1ED2DC5600515810 /* AppDelegate.swift */; };
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FA1CF9000F007C117D /* Main.storyboard */; };
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FD1CF9000F007C117D /* Assets.xcassets */; };
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */; };
|
||||||
|
DDEFBAAB26AAE3AA00159A13 /* Pikapi.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = DDEFBAAA26AAE3AA00159A13 /* Pikapi.framework */; };
|
||||||
|
/* End PBXBuildFile section */
|
||||||
|
|
||||||
|
/* Begin PBXCopyFilesBuildPhase section */
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */ = {
|
||||||
|
isa = PBXCopyFilesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
dstPath = "";
|
||||||
|
dstSubfolderSpec = 10;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
name = "Embed Frameworks";
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXCopyFilesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXFileReference section */
|
||||||
|
1001C50AAB0DFA884ACAD48C /* Pods-Runner.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.release.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.release.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GeneratedPluginRegistrant.h; sourceTree = "<group>"; };
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = GeneratedPluginRegistrant.m; sourceTree = "<group>"; };
|
||||||
|
3742BDBA4B7EA3162E2CDC75 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; name = AppFrameworkInfo.plist; path = Flutter/AppFrameworkInfo.plist; sourceTree = "<group>"; };
|
||||||
|
605DB0C59210B25A843453FD /* Pods_Runner.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Runner.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Runner-Bridging-Header.h"; sourceTree = "<group>"; };
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; name = Release.xcconfig; path = Flutter/Release.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Debug.xcconfig; path = Flutter/Debug.xcconfig; sourceTree = "<group>"; };
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.xcconfig; name = Generated.xcconfig; path = Flutter/Generated.xcconfig; sourceTree = "<group>"; };
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Runner.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||||
|
97C146FB1CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
|
97C147001CF9000F007C117D /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = "<group>"; };
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
|
CA7EB5DA1FDE22BAC5B01D77 /* Pods-Runner.profile.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.profile.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.profile.xcconfig"; sourceTree = "<group>"; };
|
||||||
|
DDEFBAAA26AAE3AA00159A13 /* Pikapi.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Pikapi.framework; path = ../go/mobile/lib/Pikapi.framework; sourceTree = "<group>"; };
|
||||||
|
/* End PBXFileReference section */
|
||||||
|
|
||||||
|
/* Begin PBXFrameworksBuildPhase section */
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */ = {
|
||||||
|
isa = PBXFrameworksBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
DDEFBAAB26AAE3AA00159A13 /* Pikapi.framework in Frameworks */,
|
||||||
|
0E44DEFD92B805627806403C /* Pods_Runner.framework in Frameworks */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXFrameworksBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXGroup section */
|
||||||
|
6CBB90743F7578DFC9C6BF75 /* Pods */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3742BDBA4B7EA3162E2CDC75 /* Pods-Runner.debug.xcconfig */,
|
||||||
|
1001C50AAB0DFA884ACAD48C /* Pods-Runner.release.xcconfig */,
|
||||||
|
CA7EB5DA1FDE22BAC5B01D77 /* Pods-Runner.profile.xcconfig */,
|
||||||
|
);
|
||||||
|
path = Pods;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */,
|
||||||
|
9740EEB21CF90195004384FC /* Debug.xcconfig */,
|
||||||
|
7AFA3C8E1D35360C0083082E /* Release.xcconfig */,
|
||||||
|
9740EEB31CF90195004384FC /* Generated.xcconfig */,
|
||||||
|
);
|
||||||
|
name = Flutter;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146E51CF9000F007C117D = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
DDEFBAAA26AAE3AA00159A13 /* Pikapi.framework */,
|
||||||
|
9740EEB11CF90186004384FC /* Flutter */,
|
||||||
|
97C146F01CF9000F007C117D /* Runner */,
|
||||||
|
97C146EF1CF9000F007C117D /* Products */,
|
||||||
|
6CBB90743F7578DFC9C6BF75 /* Pods */,
|
||||||
|
F6DB48AA376F5D49016BEA7A /* Frameworks */,
|
||||||
|
);
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146EF1CF9000F007C117D /* Products */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146EE1CF9000F007C117D /* Runner.app */,
|
||||||
|
);
|
||||||
|
name = Products;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146F01CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */,
|
||||||
|
97C146FD1CF9000F007C117D /* Assets.xcassets */,
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */,
|
||||||
|
97C147021CF9000F007C117D /* Info.plist */,
|
||||||
|
1498D2321E8E86230040F4C2 /* GeneratedPluginRegistrant.h */,
|
||||||
|
1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */,
|
||||||
|
74858FAE1ED2DC5600515810 /* AppDelegate.swift */,
|
||||||
|
74858FAD1ED2DC5600515810 /* Runner-Bridging-Header.h */,
|
||||||
|
);
|
||||||
|
path = Runner;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
F6DB48AA376F5D49016BEA7A /* Frameworks */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
605DB0C59210B25A843453FD /* Pods_Runner.framework */,
|
||||||
|
);
|
||||||
|
name = Frameworks;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXGroup section */
|
||||||
|
|
||||||
|
/* Begin PBXNativeTarget section */
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */ = {
|
||||||
|
isa = PBXNativeTarget;
|
||||||
|
buildConfigurationList = 97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */;
|
||||||
|
buildPhases = (
|
||||||
|
6D683F8ECDB7CFFB7E7E554B /* [CP] Check Pods Manifest.lock */,
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */,
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */,
|
||||||
|
97C146EB1CF9000F007C117D /* Frameworks */,
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */,
|
||||||
|
9705A1C41CF9048500538489 /* Embed Frameworks */,
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */,
|
||||||
|
A66AE356A44E049B8DF0FD4F /* [CP] Embed Pods Frameworks */,
|
||||||
|
);
|
||||||
|
buildRules = (
|
||||||
|
);
|
||||||
|
dependencies = (
|
||||||
|
);
|
||||||
|
name = Runner;
|
||||||
|
productName = Runner;
|
||||||
|
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||||
|
productType = "com.apple.product-type.application";
|
||||||
|
};
|
||||||
|
/* End PBXNativeTarget section */
|
||||||
|
|
||||||
|
/* Begin PBXProject section */
|
||||||
|
97C146E61CF9000F007C117D /* Project object */ = {
|
||||||
|
isa = PBXProject;
|
||||||
|
attributes = {
|
||||||
|
LastUpgradeCheck = 1020;
|
||||||
|
ORGANIZATIONNAME = "";
|
||||||
|
TargetAttributes = {
|
||||||
|
97C146ED1CF9000F007C117D = {
|
||||||
|
CreatedOnToolsVersion = 7.3.1;
|
||||||
|
LastSwiftMigration = 1100;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
};
|
||||||
|
buildConfigurationList = 97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */;
|
||||||
|
compatibilityVersion = "Xcode 9.3";
|
||||||
|
developmentRegion = en;
|
||||||
|
hasScannedForEncodings = 0;
|
||||||
|
knownRegions = (
|
||||||
|
en,
|
||||||
|
Base,
|
||||||
|
);
|
||||||
|
mainGroup = 97C146E51CF9000F007C117D;
|
||||||
|
productRefGroup = 97C146EF1CF9000F007C117D /* Products */;
|
||||||
|
projectDirPath = "";
|
||||||
|
projectRoot = "";
|
||||||
|
targets = (
|
||||||
|
97C146ED1CF9000F007C117D /* Runner */,
|
||||||
|
);
|
||||||
|
};
|
||||||
|
/* End PBXProject section */
|
||||||
|
|
||||||
|
/* Begin PBXResourcesBuildPhase section */
|
||||||
|
97C146EC1CF9000F007C117D /* Resources */ = {
|
||||||
|
isa = PBXResourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
97C147011CF9000F007C117D /* LaunchScreen.storyboard in Resources */,
|
||||||
|
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */,
|
||||||
|
97C146FE1CF9000F007C117D /* Assets.xcassets in Resources */,
|
||||||
|
97C146FC1CF9000F007C117D /* Main.storyboard in Resources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXResourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXShellScriptBuildPhase section */
|
||||||
|
3B06AD1E1E4923F5004D2608 /* Thin Binary */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Thin Binary";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" embed_and_thin";
|
||||||
|
};
|
||||||
|
6D683F8ECDB7CFFB7E7E554B /* [CP] Check Pods Manifest.lock */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
"${PODS_PODFILE_DIR_PATH}/Podfile.lock",
|
||||||
|
"${PODS_ROOT}/Manifest.lock",
|
||||||
|
);
|
||||||
|
name = "[CP] Check Pods Manifest.lock";
|
||||||
|
outputFileListPaths = (
|
||||||
|
);
|
||||||
|
outputPaths = (
|
||||||
|
"$(DERIVED_FILE_DIR)/Pods-Runner-checkManifestLockResult.txt",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
9740EEB61CF901F6004384FC /* Run Script */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputPaths = (
|
||||||
|
);
|
||||||
|
name = "Run Script";
|
||||||
|
outputPaths = (
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "/bin/sh \"$FLUTTER_ROOT/packages/flutter_tools/bin/xcode_backend.sh\" build";
|
||||||
|
};
|
||||||
|
A66AE356A44E049B8DF0FD4F /* [CP] Embed Pods Frameworks */ = {
|
||||||
|
isa = PBXShellScriptBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
);
|
||||||
|
inputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
|
||||||
|
);
|
||||||
|
name = "[CP] Embed Pods Frameworks";
|
||||||
|
outputFileListPaths = (
|
||||||
|
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
shellPath = /bin/sh;
|
||||||
|
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
|
||||||
|
showEnvVarsInLog = 0;
|
||||||
|
};
|
||||||
|
/* End PBXShellScriptBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXSourcesBuildPhase section */
|
||||||
|
97C146EA1CF9000F007C117D /* Sources */ = {
|
||||||
|
isa = PBXSourcesBuildPhase;
|
||||||
|
buildActionMask = 2147483647;
|
||||||
|
files = (
|
||||||
|
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||||
|
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||||
|
);
|
||||||
|
runOnlyForDeploymentPostprocessing = 0;
|
||||||
|
};
|
||||||
|
/* End PBXSourcesBuildPhase section */
|
||||||
|
|
||||||
|
/* Begin PBXVariantGroup section */
|
||||||
|
97C146FA1CF9000F007C117D /* Main.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C146FB1CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = Main.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
97C146FF1CF9000F007C117D /* LaunchScreen.storyboard */ = {
|
||||||
|
isa = PBXVariantGroup;
|
||||||
|
children = (
|
||||||
|
97C147001CF9000F007C117D /* Base */,
|
||||||
|
);
|
||||||
|
name = LaunchScreen.storyboard;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
/* End PBXVariantGroup section */
|
||||||
|
|
||||||
|
/* Begin XCBuildConfiguration section */
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = "../go/mobile/lib/**";
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapi;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Profile;
|
||||||
|
};
|
||||||
|
97C147031CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = dwarf;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
ENABLE_TESTABILITY = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = "../go/mobile/lib/**";
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_DYNAMIC_NO_PIC = NO;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_OPTIMIZATION_LEVEL = 0;
|
||||||
|
GCC_PREPROCESSOR_DEFINITIONS = (
|
||||||
|
"DEBUG=1",
|
||||||
|
"$(inherited)",
|
||||||
|
);
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = YES;
|
||||||
|
ONLY_ACTIVE_ARCH = YES;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147041CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
buildSettings = {
|
||||||
|
ALWAYS_SEARCH_USER_PATHS = NO;
|
||||||
|
CLANG_ANALYZER_NONNULL = YES;
|
||||||
|
CLANG_CXX_LANGUAGE_STANDARD = "gnu++0x";
|
||||||
|
CLANG_CXX_LIBRARY = "libc++";
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CLANG_ENABLE_OBJC_ARC = YES;
|
||||||
|
CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES;
|
||||||
|
CLANG_WARN_BOOL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_COMMA = YES;
|
||||||
|
CLANG_WARN_CONSTANT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES;
|
||||||
|
CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR;
|
||||||
|
CLANG_WARN_EMPTY_BODY = YES;
|
||||||
|
CLANG_WARN_ENUM_CONVERSION = YES;
|
||||||
|
CLANG_WARN_INFINITE_RECURSION = YES;
|
||||||
|
CLANG_WARN_INT_CONVERSION = YES;
|
||||||
|
CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES;
|
||||||
|
CLANG_WARN_OBJC_LITERAL_CONVERSION = YES;
|
||||||
|
CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR;
|
||||||
|
CLANG_WARN_RANGE_LOOP_ANALYSIS = YES;
|
||||||
|
CLANG_WARN_STRICT_PROTOTYPES = YES;
|
||||||
|
CLANG_WARN_SUSPICIOUS_MOVE = YES;
|
||||||
|
CLANG_WARN_UNREACHABLE_CODE = YES;
|
||||||
|
CLANG_WARN__DUPLICATE_METHOD_MATCH = YES;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer";
|
||||||
|
COPY_PHASE_STRIP = NO;
|
||||||
|
DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym";
|
||||||
|
ENABLE_NS_ASSERTIONS = NO;
|
||||||
|
ENABLE_STRICT_OBJC_MSGSEND = YES;
|
||||||
|
FRAMEWORK_SEARCH_PATHS = "../go/mobile/lib/**";
|
||||||
|
GCC_C_LANGUAGE_STANDARD = gnu99;
|
||||||
|
GCC_NO_COMMON_BLOCKS = YES;
|
||||||
|
GCC_WARN_64_TO_32_BIT_CONVERSION = YES;
|
||||||
|
GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR;
|
||||||
|
GCC_WARN_UNDECLARED_SELECTOR = YES;
|
||||||
|
GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE;
|
||||||
|
GCC_WARN_UNUSED_FUNCTION = YES;
|
||||||
|
GCC_WARN_UNUSED_VARIABLE = YES;
|
||||||
|
IPHONEOS_DEPLOYMENT_TARGET = 10.0;
|
||||||
|
MTL_ENABLE_DEBUG_INFO = NO;
|
||||||
|
SDKROOT = iphoneos;
|
||||||
|
SUPPORTED_PLATFORMS = iphoneos;
|
||||||
|
SWIFT_COMPILATION_MODE = wholemodule;
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-O";
|
||||||
|
TARGETED_DEVICE_FAMILY = "1,2";
|
||||||
|
VALIDATE_PRODUCT = YES;
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
97C147061CF9000F007C117D /* Debug */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapi;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_OPTIMIZATION_LEVEL = "-Onone";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Debug;
|
||||||
|
};
|
||||||
|
97C147071CF9000F007C117D /* Release */ = {
|
||||||
|
isa = XCBuildConfiguration;
|
||||||
|
baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
|
||||||
|
buildSettings = {
|
||||||
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
|
CLANG_ENABLE_MODULES = YES;
|
||||||
|
CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
|
||||||
|
ENABLE_BITCODE = NO;
|
||||||
|
INFOPLIST_FILE = Runner/Info.plist;
|
||||||
|
LD_RUNPATH_SEARCH_PATHS = (
|
||||||
|
"$(inherited)",
|
||||||
|
"@executable_path/Frameworks",
|
||||||
|
);
|
||||||
|
PRODUCT_BUNDLE_IDENTIFIER = niuhuan.pikapi;
|
||||||
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
|
SWIFT_OBJC_BRIDGING_HEADER = "Runner/Runner-Bridging-Header.h";
|
||||||
|
SWIFT_VERSION = 5.0;
|
||||||
|
VERSIONING_SYSTEM = "apple-generic";
|
||||||
|
};
|
||||||
|
name = Release;
|
||||||
|
};
|
||||||
|
/* End XCBuildConfiguration section */
|
||||||
|
|
||||||
|
/* Begin XCConfigurationList section */
|
||||||
|
97C146E91CF9000F007C117D /* Build configuration list for PBXProject "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147031CF9000F007C117D /* Debug */,
|
||||||
|
97C147041CF9000F007C117D /* Release */,
|
||||||
|
249021D3217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
97C147051CF9000F007C117D /* Build configuration list for PBXNativeTarget "Runner" */ = {
|
||||||
|
isa = XCConfigurationList;
|
||||||
|
buildConfigurations = (
|
||||||
|
97C147061CF9000F007C117D /* Debug */,
|
||||||
|
97C147071CF9000F007C117D /* Release */,
|
||||||
|
249021D4217E4FDB00AE95B9 /* Profile */,
|
||||||
|
);
|
||||||
|
defaultConfigurationIsVisible = 0;
|
||||||
|
defaultConfigurationName = Release;
|
||||||
|
};
|
||||||
|
/* End XCConfigurationList section */
|
||||||
|
};
|
||||||
|
rootObject = 97C146E61CF9000F007C117D /* Project object */;
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "self:">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,91 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Scheme
|
||||||
|
LastUpgradeVersion = "1020"
|
||||||
|
version = "1.3">
|
||||||
|
<BuildAction
|
||||||
|
parallelizeBuildables = "YES"
|
||||||
|
buildImplicitDependencies = "YES">
|
||||||
|
<BuildActionEntries>
|
||||||
|
<BuildActionEntry
|
||||||
|
buildForTesting = "YES"
|
||||||
|
buildForRunning = "YES"
|
||||||
|
buildForProfiling = "YES"
|
||||||
|
buildForArchiving = "YES"
|
||||||
|
buildForAnalyzing = "YES">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildActionEntry>
|
||||||
|
</BuildActionEntries>
|
||||||
|
</BuildAction>
|
||||||
|
<TestAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||||
|
<Testables>
|
||||||
|
</Testables>
|
||||||
|
<MacroExpansion>
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</MacroExpansion>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</TestAction>
|
||||||
|
<LaunchAction
|
||||||
|
buildConfiguration = "Debug"
|
||||||
|
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||||
|
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||||
|
launchStyle = "0"
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
ignoresPersistentStateOnLaunch = "NO"
|
||||||
|
debugDocumentVersioning = "YES"
|
||||||
|
debugServiceExtension = "internal"
|
||||||
|
allowLocationSimulation = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
<AdditionalOptions>
|
||||||
|
</AdditionalOptions>
|
||||||
|
</LaunchAction>
|
||||||
|
<ProfileAction
|
||||||
|
buildConfiguration = "Profile"
|
||||||
|
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||||
|
savedToolIdentifier = ""
|
||||||
|
useCustomWorkingDirectory = "NO"
|
||||||
|
debugDocumentVersioning = "YES">
|
||||||
|
<BuildableProductRunnable
|
||||||
|
runnableDebuggingMode = "0">
|
||||||
|
<BuildableReference
|
||||||
|
BuildableIdentifier = "primary"
|
||||||
|
BlueprintIdentifier = "97C146ED1CF9000F007C117D"
|
||||||
|
BuildableName = "Runner.app"
|
||||||
|
BlueprintName = "Runner"
|
||||||
|
ReferencedContainer = "container:Runner.xcodeproj">
|
||||||
|
</BuildableReference>
|
||||||
|
</BuildableProductRunnable>
|
||||||
|
</ProfileAction>
|
||||||
|
<AnalyzeAction
|
||||||
|
buildConfiguration = "Debug">
|
||||||
|
</AnalyzeAction>
|
||||||
|
<ArchiveAction
|
||||||
|
buildConfiguration = "Release"
|
||||||
|
revealArchiveInOrganizer = "YES">
|
||||||
|
</ArchiveAction>
|
||||||
|
</Scheme>
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<Workspace
|
||||||
|
version = "1.0">
|
||||||
|
<FileRef
|
||||||
|
location = "group:Runner.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
<FileRef
|
||||||
|
location = "group:Pods/Pods.xcodeproj">
|
||||||
|
</FileRef>
|
||||||
|
</Workspace>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>IDEDidComputeMac32BitWarning</key>
|
||||||
|
<true/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict>
|
||||||
|
<key>PreviewsEnabled</key>
|
||||||
|
<false/>
|
||||||
|
</dict>
|
||||||
|
</plist>
|
|
@ -0,0 +1,104 @@
|
||||||
|
import UIKit
|
||||||
|
import Flutter
|
||||||
|
import Pikapi
|
||||||
|
|
||||||
|
@UIApplicationMain
|
||||||
|
@objc class AppDelegate: FlutterAppDelegate {
|
||||||
|
override func application(
|
||||||
|
_ application: UIApplication,
|
||||||
|
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
|
||||||
|
) -> Bool {
|
||||||
|
|
||||||
|
let documentsPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true)[0]
|
||||||
|
|
||||||
|
MobileInitApplication(documentsPath)
|
||||||
|
|
||||||
|
let controller = self.window.rootViewController as! FlutterViewController
|
||||||
|
let channel = FlutterMethodChannel.init(name: "method", binaryMessenger: controller as! FlutterBinaryMessenger)
|
||||||
|
|
||||||
|
channel.setMethodCallHandler { (call, result) in
|
||||||
|
Thread {
|
||||||
|
if call.method == "flatInvoke" {
|
||||||
|
if let args = call.arguments as? Dictionary<String, Any>,
|
||||||
|
let method = args["method"] as? String,
|
||||||
|
let params = args["params"] as? String{
|
||||||
|
var error: NSError?
|
||||||
|
let data = MobileFlatInvoke(method, params, &error)
|
||||||
|
if error != nil {
|
||||||
|
result(FlutterError(code: "", message: error?.localizedDescription, details: ""))
|
||||||
|
}else{
|
||||||
|
result(data)
|
||||||
|
}
|
||||||
|
}else{
|
||||||
|
result(FlutterError(code: "", message: "params error", details: ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if call.method == "iosSaveFileToImage"{
|
||||||
|
if let args = call.arguments as? Dictionary<String, Any>,
|
||||||
|
let path = args["path"] as? String{
|
||||||
|
|
||||||
|
do {
|
||||||
|
let fileURL: URL = URL(fileURLWithPath: path)
|
||||||
|
let imageData = try Data(contentsOf: fileURL)
|
||||||
|
|
||||||
|
if let uiImage = UIImage(data: imageData) {
|
||||||
|
UIImageWriteToSavedPhotosAlbum(uiImage, nil, nil, nil)
|
||||||
|
result("OK")
|
||||||
|
}else{
|
||||||
|
result(FlutterError(code: "", message: "Error loading image ", details: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch {
|
||||||
|
result(FlutterError(code: "", message: "Error loading image : \(error)", details: ""))
|
||||||
|
}
|
||||||
|
|
||||||
|
}else{
|
||||||
|
result(FlutterError(code: "", message: "params error", details: ""))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else{
|
||||||
|
result(FlutterMethodNotImplemented)
|
||||||
|
}
|
||||||
|
}.start()
|
||||||
|
}
|
||||||
|
|
||||||
|
//
|
||||||
|
let eventChannel = FlutterEventChannel.init(name: "flatEvent", binaryMessenger: controller as! FlutterBinaryMessenger)
|
||||||
|
|
||||||
|
class EventChannelHandler:NSObject, FlutterStreamHandler {
|
||||||
|
func onListen(withArguments arguments: Any?, eventSink events: @escaping FlutterEventSink) -> FlutterError? {
|
||||||
|
objc_sync_enter(mutex)
|
||||||
|
sink = events
|
||||||
|
objc_sync_exit(mutex)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func onCancel(withArguments arguments: Any?) -> FlutterError? {
|
||||||
|
objc_sync_enter(mutex)
|
||||||
|
sink = nil
|
||||||
|
objc_sync_exit(mutex)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
class EventNotifyHandler:NSObject, MobileEventNotifyHandlerProtocol {
|
||||||
|
func onNotify(_ message: String?) {
|
||||||
|
objc_sync_enter(mutex)
|
||||||
|
if sink != nil {
|
||||||
|
sink?(message)
|
||||||
|
}
|
||||||
|
objc_sync_exit(mutex)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
eventChannel.setStreamHandler(EventChannelHandler.init())
|
||||||
|
MobileEventNotify(EventNotifyHandler.init())
|
||||||
|
|
||||||
|
//
|
||||||
|
GeneratedPluginRegistrant.register(with: self)
|
||||||
|
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
var sink : FlutterEventSink?
|
||||||
|
let mutex = NSObject.init()
|
||||||
|
|
|
@ -0,0 +1,122 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-20x20@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-29x29@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-40x40@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "60x60",
|
||||||
|
"idiom" : "iphone",
|
||||||
|
"filename" : "Icon-App-60x60@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "20x20",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-20x20@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "29x29",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-29x29@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "40x40",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-40x40@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "76x76",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-76x76@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "83.5x83.5",
|
||||||
|
"idiom" : "ipad",
|
||||||
|
"filename" : "Icon-App-83.5x83.5@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"size" : "1024x1024",
|
||||||
|
"idiom" : "ios-marketing",
|
||||||
|
"filename" : "Icon-App-1024x1024@1x.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|
After Width: | Height: | Size: 174 KiB |
After Width: | Height: | Size: 947 B |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 6.5 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 5.7 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 9.8 KiB |
After Width: | Height: | Size: 16 KiB |
After Width: | Height: | Size: 5.6 KiB |
After Width: | Height: | Size: 13 KiB |
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,23 @@
|
||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage.png",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@2x.png",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"filename" : "LaunchImage@3x.png",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"version" : 1,
|
||||||
|
"author" : "xcode"
|
||||||
|
}
|
||||||
|
}
|