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"
|
||||
}
|
||||
}
|