support andoird
12
.gitignore
vendored
@@ -1,10 +1,18 @@
|
|||||||
__debug_bin
|
__debug_bin
|
||||||
__debug_bin.exe
|
__debug_bin.exe
|
||||||
# .vscode
|
# .vscode
|
||||||
openp2p
|
test/
|
||||||
openp2p.exe*
|
openp2p.exe*
|
||||||
*.log
|
*.log*
|
||||||
go.sum
|
go.sum
|
||||||
*.tar.gz
|
*.tar.gz
|
||||||
*.zip
|
*.zip
|
||||||
*.exe
|
*.exe
|
||||||
|
config.json
|
||||||
|
libs/
|
||||||
|
*/app/.idea/
|
||||||
|
*/app/release/
|
||||||
|
openp2p.app.jks
|
||||||
|
openp2p.aar
|
||||||
|
openp2p-sources.jar
|
||||||
|
build.gradle
|
||||||
|
7
Makefile
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
build:
|
||||||
|
export GOPROXY=https://goproxy.io,direct
|
||||||
|
go mod tidy
|
||||||
|
go build cmd/openp2p.go
|
||||||
|
.PHONY: build
|
||||||
|
|
||||||
|
.DEFAULT_GOAL := build
|
@@ -31,6 +31,7 @@ P2P直连可以让你的设备跑满带宽。不论你的设备在任何网络
|
|||||||
## 快速入门
|
## 快速入门
|
||||||
仅需简单4步就能用起来。
|
仅需简单4步就能用起来。
|
||||||
下面是一个远程办公例子:在家里连入办公室Windows电脑。
|
下面是一个远程办公例子:在家里连入办公室Windows电脑。
|
||||||
|
(另外一个快速入门视频 https://www.bilibili.com/video/BV1Et4y1P7bF/)
|
||||||
### 1.注册
|
### 1.注册
|
||||||
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
|
前往<https://console.openp2p.cn> 注册新用户,暂无需任何认证
|
||||||
|
|
||||||
@@ -98,9 +99,7 @@ Windows默认会阻止没有花钱买它家证书签名过的程序,选择“
|
|||||||
go version go1.18.1+
|
go version go1.18.1+
|
||||||
cd到代码根目录,执行
|
cd到代码根目录,执行
|
||||||
```
|
```
|
||||||
export GOPROXY=https://goproxy.io,direct
|
make
|
||||||
go mod tidy
|
|
||||||
go build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## RoadMap
|
## RoadMap
|
||||||
|
@@ -33,7 +33,7 @@ Your applicaiton can call OpenP2P with a few code to make any internal networks
|
|||||||
## Get Started
|
## Get Started
|
||||||
Just 4 simple steps to use.
|
Just 4 simple steps to use.
|
||||||
Here's an example of remote work: connecting to an office Windows computer at home.
|
Here's an example of remote work: connecting to an office Windows computer at home.
|
||||||
|
(Another quick started vedio https://www.bilibili.com/video/BV1Et4y1P7bF/)
|
||||||
### 1.Register
|
### 1.Register
|
||||||
Go to <https://console.openp2p.cn> register a new user
|
Go to <https://console.openp2p.cn> register a new user
|
||||||
|
|
||||||
@@ -106,9 +106,7 @@ The server side has a scheduling model, which calculate bandwith, ping value,st
|
|||||||
go version go1.18.1+
|
go version go1.18.1+
|
||||||
cd root directory of the socure code and execute
|
cd root directory of the socure code and execute
|
||||||
```
|
```
|
||||||
export GOPROXY=https://goproxy.io,direct
|
make
|
||||||
go mod tidy
|
|
||||||
go build
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## RoadMap
|
## RoadMap
|
||||||
|
15
app/.gitignore
vendored
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
*.iml
|
||||||
|
.gradle
|
||||||
|
/local.properties
|
||||||
|
/.idea/caches
|
||||||
|
/.idea/libraries
|
||||||
|
/.idea/modules.xml
|
||||||
|
/.idea/workspace.xml
|
||||||
|
/.idea/navEditor.xml
|
||||||
|
/.idea/assetWizardSettings.xml
|
||||||
|
.DS_Store
|
||||||
|
/build
|
||||||
|
/captures
|
||||||
|
.externalNativeBuild
|
||||||
|
.cxx
|
||||||
|
local.properties
|
3
app/.idea/.gitignore
generated
vendored
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
1
app/.idea/.name
generated
Normal file
@@ -0,0 +1 @@
|
|||||||
|
OpenP2P
|
6
app/.idea/compiler.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="CompilerConfiguration">
|
||||||
|
<bytecodeTargetLevel target="11" />
|
||||||
|
</component>
|
||||||
|
</project>
|
19
app/.idea/gradle.xml
generated
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="GradleSettings">
|
||||||
|
<option name="linkedExternalProjectsSettings">
|
||||||
|
<GradleProjectSettings>
|
||||||
|
<option name="testRunner" value="PLATFORM" />
|
||||||
|
<option name="distributionType" value="DEFAULT_WRAPPED" />
|
||||||
|
<option name="externalProjectPath" value="$PROJECT_DIR$" />
|
||||||
|
<option name="modules">
|
||||||
|
<set>
|
||||||
|
<option value="$PROJECT_DIR$" />
|
||||||
|
<option value="$PROJECT_DIR$/app" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
<option name="resolveModulePerSourceSet" value="false" />
|
||||||
|
</GradleProjectSettings>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
30
app/.idea/jarRepositories.xml
generated
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RemoteRepositoriesConfiguration">
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="central" />
|
||||||
|
<option name="name" value="Maven Central repository" />
|
||||||
|
<option name="url" value="https://repo1.maven.org/maven2" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="jboss.community" />
|
||||||
|
<option name="name" value="JBoss Community repository" />
|
||||||
|
<option name="url" value="https://repository.jboss.org/nexus/content/repositories/public/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="MavenRepo" />
|
||||||
|
<option name="name" value="MavenRepo" />
|
||||||
|
<option name="url" value="https://repo.maven.apache.org/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="BintrayJCenter" />
|
||||||
|
<option name="name" value="BintrayJCenter" />
|
||||||
|
<option name="url" value="https://jcenter.bintray.com/" />
|
||||||
|
</remote-repository>
|
||||||
|
<remote-repository>
|
||||||
|
<option name="id" value="Google" />
|
||||||
|
<option name="name" value="Google" />
|
||||||
|
<option name="url" value="https://dl.google.com/dl/android/maven2/" />
|
||||||
|
</remote-repository>
|
||||||
|
</component>
|
||||||
|
</project>
|
9
app/.idea/misc.xml
generated
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectRootManager" version="2" languageLevel="JDK_11" default="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
|
||||||
|
<output url="file://$PROJECT_DIR$/build/classes" />
|
||||||
|
</component>
|
||||||
|
<component name="ProjectType">
|
||||||
|
<option name="id" value="Android" />
|
||||||
|
</component>
|
||||||
|
</project>
|
10
app/.idea/runConfigurations.xml
generated
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="RunConfigurationProducerService">
|
||||||
|
<option name="ignoredProducers">
|
||||||
|
<set>
|
||||||
|
<option value="com.android.tools.idea.compose.preview.runconfiguration.ComposePreviewRunConfigurationProducer" />
|
||||||
|
</set>
|
||||||
|
</option>
|
||||||
|
</component>
|
||||||
|
</project>
|
6
app/.idea/vcs.xml
generated
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
1
app/app/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
|||||||
|
/build
|
21
app/app/proguard-rules.pro
vendored
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
# Add project specific ProGuard rules here.
|
||||||
|
# You can control the set of applied configuration files using the
|
||||||
|
# proguardFiles setting in build.gradle.
|
||||||
|
#
|
||||||
|
# For more details, see
|
||||||
|
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||||
|
|
||||||
|
# If your project uses WebView with JS, uncomment the following
|
||||||
|
# and specify the fully qualified class name to the JavaScript interface
|
||||||
|
# class:
|
||||||
|
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||||
|
# public *;
|
||||||
|
#}
|
||||||
|
|
||||||
|
# Uncomment this to preserve the line number information for
|
||||||
|
# debugging stack traces.
|
||||||
|
#-keepattributes SourceFile,LineNumberTable
|
||||||
|
|
||||||
|
# If you keep the line number information, uncomment this to
|
||||||
|
# hide the original source file name.
|
||||||
|
#-renamesourcefileattribute SourceFile
|
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.openp2p
|
||||||
|
|
||||||
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
import org.junit.Assert.*
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instrumented test, which will execute on an Android device.
|
||||||
|
*
|
||||||
|
* See [testing documentation](http://d.android.com/tools/testing).
|
||||||
|
*/
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class ExampleInstrumentedTest {
|
||||||
|
@Test
|
||||||
|
fun useAppContext() {
|
||||||
|
// Context of the app under test.
|
||||||
|
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
|
||||||
|
assertEquals("cn.openp2p", appContext.packageName)
|
||||||
|
}
|
||||||
|
}
|
39
app/app/src/main/AndroidManifest.xml
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
package="cn.openp2p">
|
||||||
|
|
||||||
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
|
||||||
|
<uses-permission android:name="android.permission.INTERNET" />
|
||||||
|
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
|
||||||
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
|
|
||||||
|
<application
|
||||||
|
android:allowBackup="true"
|
||||||
|
android:icon="@mipmap/ic_launcher"
|
||||||
|
android:label="@string/app_name"
|
||||||
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
|
android:supportsRtl="true"
|
||||||
|
android:theme="@style/Theme.OpenP2P">
|
||||||
|
<service
|
||||||
|
android:name=".OpenP2PService"
|
||||||
|
android:enabled="true"
|
||||||
|
android:exported="true"
|
||||||
|
android:permission="android.permission.BIND_VPN_SERVICE">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.net.VpnService"/>
|
||||||
|
</intent-filter>
|
||||||
|
</service>
|
||||||
|
|
||||||
|
<activity
|
||||||
|
android:name=".ui.login.LoginActivity"
|
||||||
|
android:label="@string/app_name">
|
||||||
|
<intent-filter>
|
||||||
|
<action android:name="android.intent.action.MAIN" />
|
||||||
|
|
||||||
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
|
</intent-filter>
|
||||||
|
</activity>
|
||||||
|
</application>
|
||||||
|
|
||||||
|
</manifest>
|
59
app/app/src/main/java/cn/openp2p/OpenP2PService.kt
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
package cn.openp2p
|
||||||
|
|
||||||
|
import android.app.Service
|
||||||
|
import android.content.Intent
|
||||||
|
import android.net.VpnService
|
||||||
|
import android.os.Binder
|
||||||
|
import android.os.IBinder
|
||||||
|
import openp2p.Openp2p
|
||||||
|
import android.util.Log
|
||||||
|
import cn.openp2p.ui.login.LoginActivity
|
||||||
|
import kotlinx.coroutines.GlobalScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
|
||||||
|
class OpenP2PService : VpnService() {
|
||||||
|
companion object {
|
||||||
|
private val LOG_TAG = OpenP2PService::class.simpleName
|
||||||
|
}
|
||||||
|
|
||||||
|
inner class LocalBinder : Binder() {
|
||||||
|
fun getService(): OpenP2PService = this@OpenP2PService
|
||||||
|
}
|
||||||
|
|
||||||
|
private val binder = LocalBinder()
|
||||||
|
private lateinit var network: openp2p.P2PNetwork
|
||||||
|
override fun onCreate() {
|
||||||
|
Log.i("xiao","onCreate - Thread ID = " + Thread.currentThread().id)
|
||||||
|
super.onCreate()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
|
||||||
|
Log.i("xiao", "onStartCommand - startId = " + startId + ", Thread ID = " + Thread.currentThread().id)
|
||||||
|
return super.onStartCommand(intent, flags, startId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBind(p0: Intent?): IBinder? {
|
||||||
|
return binder
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroy() {
|
||||||
|
Log.i("xiao", "onDestroy - Thread ID = " + Thread.currentThread().id)
|
||||||
|
super.onDestroy()
|
||||||
|
}
|
||||||
|
fun onStart(token:String){
|
||||||
|
GlobalScope.launch {
|
||||||
|
do {
|
||||||
|
network =Openp2p.runAsModule(getExternalFilesDir(null).toString(), token, 0)
|
||||||
|
val isConnect = network.connect(30000) // ms
|
||||||
|
Log.i(OpenP2PService.LOG_TAG, "login result: " + isConnect.toString());
|
||||||
|
} while(!network?.connect(10000))
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fun isConnected(): Boolean{
|
||||||
|
if (!::network.isInitialized) return false
|
||||||
|
return network?.connect(1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
24
app/app/src/main/java/cn/openp2p/data/LoginDataSource.kt
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
package cn.openp2p.data
|
||||||
|
|
||||||
|
import cn.openp2p.data.model.LoggedInUser
|
||||||
|
import java.io.IOException
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that handles authentication w/ login credentials and retrieves user information.
|
||||||
|
*/
|
||||||
|
class LoginDataSource {
|
||||||
|
|
||||||
|
fun login(username: String, password: String): Result<LoggedInUser> {
|
||||||
|
try {
|
||||||
|
// TODO: handle loggedInUser authentication
|
||||||
|
val fakeUser = LoggedInUser(java.util.UUID.randomUUID().toString(), "Jane Doe")
|
||||||
|
return Result.Success(fakeUser)
|
||||||
|
} catch (e: Throwable) {
|
||||||
|
return Result.Error(IOException("Error logging in", e))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
// TODO: revoke authentication
|
||||||
|
}
|
||||||
|
}
|
46
app/app/src/main/java/cn/openp2p/data/LoginRepository.kt
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package cn.openp2p.data
|
||||||
|
|
||||||
|
import cn.openp2p.data.model.LoggedInUser
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class that requests authentication and user information from the remote data source and
|
||||||
|
* maintains an in-memory cache of login status and user credentials information.
|
||||||
|
*/
|
||||||
|
|
||||||
|
class LoginRepository(val dataSource: LoginDataSource) {
|
||||||
|
|
||||||
|
// in-memory cache of the loggedInUser object
|
||||||
|
var user: LoggedInUser? = null
|
||||||
|
private set
|
||||||
|
|
||||||
|
val isLoggedIn: Boolean
|
||||||
|
get() = user != null
|
||||||
|
|
||||||
|
init {
|
||||||
|
// If user credentials will be cached in local storage, it is recommended it be encrypted
|
||||||
|
// @see https://developer.android.com/training/articles/keystore
|
||||||
|
user = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun logout() {
|
||||||
|
user = null
|
||||||
|
dataSource.logout()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun login(username: String, password: String): Result<LoggedInUser> {
|
||||||
|
// handle login
|
||||||
|
val result = dataSource.login(username, password)
|
||||||
|
|
||||||
|
if (result is Result.Success) {
|
||||||
|
setLoggedInUser(result.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setLoggedInUser(loggedInUser: LoggedInUser) {
|
||||||
|
this.user = loggedInUser
|
||||||
|
// If user credentials will be cached in local storage, it is recommended it be encrypted
|
||||||
|
// @see https://developer.android.com/training/articles/keystore
|
||||||
|
}
|
||||||
|
}
|
18
app/app/src/main/java/cn/openp2p/data/Result.kt
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package cn.openp2p.data
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A generic class that holds a value with its loading status.
|
||||||
|
* @param <T>
|
||||||
|
*/
|
||||||
|
sealed class Result<out T : Any> {
|
||||||
|
|
||||||
|
data class Success<out T : Any>(val data: T) : Result<T>()
|
||||||
|
data class Error(val exception: Exception) : Result<Nothing>()
|
||||||
|
|
||||||
|
override fun toString(): String {
|
||||||
|
return when (this) {
|
||||||
|
is Success<*> -> "Success[data=$data]"
|
||||||
|
is Error -> "Error[exception=$exception]"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,9 @@
|
|||||||
|
package cn.openp2p.data.model
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data class that captures user information for logged in users retrieved from LoginRepository
|
||||||
|
*/
|
||||||
|
data class LoggedInUser(
|
||||||
|
val userId: String,
|
||||||
|
val displayName: String
|
||||||
|
)
|
@@ -0,0 +1,9 @@
|
|||||||
|
package cn.openp2p.ui.login
|
||||||
|
|
||||||
|
/**
|
||||||
|
* User details post authentication that is exposed to the UI
|
||||||
|
*/
|
||||||
|
data class LoggedInUserView(
|
||||||
|
val displayName: String
|
||||||
|
//... other data fields that may be accessible to the UI
|
||||||
|
)
|
188
app/app/src/main/java/cn/openp2p/ui/login/LoginActivity.kt
Normal file
@@ -0,0 +1,188 @@
|
|||||||
|
package cn.openp2p.ui.login
|
||||||
|
|
||||||
|
import android.app.Activity
|
||||||
|
import android.content.ComponentName
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.ServiceConnection
|
||||||
|
import android.net.Uri
|
||||||
|
import android.net.VpnService
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.os.IBinder
|
||||||
|
import android.text.Editable
|
||||||
|
import android.text.TextWatcher
|
||||||
|
import android.view.View
|
||||||
|
import android.widget.EditText
|
||||||
|
import android.widget.Toast
|
||||||
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.lifecycle.Observer
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import cn.openp2p.OpenP2PService
|
||||||
|
import cn.openp2p.R
|
||||||
|
import cn.openp2p.databinding.ActivityLoginBinding
|
||||||
|
import openp2p.Openp2p
|
||||||
|
import kotlin.concurrent.thread
|
||||||
|
|
||||||
|
|
||||||
|
class LoginActivity : AppCompatActivity() {
|
||||||
|
companion object {
|
||||||
|
private val LOG_TAG = LoginActivity::class.simpleName
|
||||||
|
}
|
||||||
|
private val connection = object : ServiceConnection {
|
||||||
|
override fun onServiceConnected(className: ComponentName, service: IBinder) {
|
||||||
|
val binder = service as OpenP2PService.LocalBinder
|
||||||
|
mService = binder.getService()
|
||||||
|
mService.onStart(mToken)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onServiceDisconnected(className: ComponentName) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
private lateinit var loginViewModel: LoginViewModel
|
||||||
|
private lateinit var binding: ActivityLoginBinding
|
||||||
|
private lateinit var mService: OpenP2PService
|
||||||
|
private var mToken: String=""
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
|
||||||
|
binding = ActivityLoginBinding.inflate(layoutInflater)
|
||||||
|
setContentView(binding.root)
|
||||||
|
|
||||||
|
val token = binding.token
|
||||||
|
val login = binding.login
|
||||||
|
val onlineState=binding.onlineState
|
||||||
|
val openp2pLog = binding.openp2pLog
|
||||||
|
val profile = binding.profile
|
||||||
|
val loading = binding.loading
|
||||||
|
|
||||||
|
loginViewModel = ViewModelProvider(this, LoginViewModelFactory())
|
||||||
|
.get(LoginViewModel::class.java)
|
||||||
|
|
||||||
|
loginViewModel.loginFormState.observe(this@LoginActivity, Observer {
|
||||||
|
val loginState = it ?: return@Observer
|
||||||
|
|
||||||
|
// disable login button unless both username / password is valid
|
||||||
|
login.isEnabled = loginState.isDataValid
|
||||||
|
|
||||||
|
if (loginState.passwordError != null) {
|
||||||
|
token.error = getString(loginState.passwordError)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
mToken=token.text.toString()
|
||||||
|
val intent1 = VpnService.prepare(this) ?: return
|
||||||
|
loginViewModel.loginResult.observe(this@LoginActivity, Observer {
|
||||||
|
val loginResult = it ?: return@Observer
|
||||||
|
|
||||||
|
loading.visibility = View.GONE
|
||||||
|
if (loginResult.error != null) {
|
||||||
|
showLoginFailed(loginResult.error)
|
||||||
|
}
|
||||||
|
if (loginResult.success != null) {
|
||||||
|
updateUiWithUser(loginResult.success)
|
||||||
|
}
|
||||||
|
setResult(Activity.RESULT_OK)
|
||||||
|
|
||||||
|
//Complete and destroy login activity once successful
|
||||||
|
finish()
|
||||||
|
})
|
||||||
|
|
||||||
|
profile.setOnClickListener {
|
||||||
|
val url = "https://console.openp2p.cn/profile"
|
||||||
|
val i = Intent(Intent.ACTION_VIEW)
|
||||||
|
i.data = Uri.parse(url)
|
||||||
|
startActivity(i)
|
||||||
|
}
|
||||||
|
token.apply {
|
||||||
|
afterTextChanged {
|
||||||
|
loginViewModel.loginDataChanged(
|
||||||
|
"username.text.toString()",
|
||||||
|
token.text.toString()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// setOnEditorActionListener { _, actionId, _ ->
|
||||||
|
// when (actionId) {
|
||||||
|
// EditorInfo.IME_ACTION_DONE ->
|
||||||
|
// loginViewModel.login(
|
||||||
|
// "username.text.toString()",
|
||||||
|
// token.text.toString()
|
||||||
|
// )
|
||||||
|
// }
|
||||||
|
// false
|
||||||
|
// }
|
||||||
|
|
||||||
|
// openp2pLog.setText(getExternalFilesDir(null).toString())
|
||||||
|
openp2pLog.setText(R.string.phone_setting)
|
||||||
|
token.setText(Openp2p.getToken(getExternalFilesDir(null).toString()))
|
||||||
|
login.setOnClickListener {
|
||||||
|
// loading.visibility = View.VISIBLE
|
||||||
|
// loginViewModel.login(username.text.toString(), password.text.toString())
|
||||||
|
|
||||||
|
// startService(Intent(this, OpenP2PService::class.java))
|
||||||
|
val intent = Intent(this@LoginActivity,OpenP2PService::class.java)
|
||||||
|
|
||||||
|
bindService(intent, connection, Context.BIND_AUTO_CREATE)
|
||||||
|
thread {
|
||||||
|
do {
|
||||||
|
Thread.sleep(3000)
|
||||||
|
if (!::mService.isInitialized) continue
|
||||||
|
val isConnect = mService.isConnected()
|
||||||
|
runOnUiThread {
|
||||||
|
if (isConnect) {
|
||||||
|
onlineState.setText("在线")
|
||||||
|
} else {
|
||||||
|
onlineState.setText("离线")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} while(true)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// fun listenProgress() {
|
||||||
|
// Thread {
|
||||||
|
// while (progress < MsgService.MAX_PROGRESS) {
|
||||||
|
// progress = msgService.getProgress()
|
||||||
|
// mProgressBar.setProgress(progress)
|
||||||
|
// try {
|
||||||
|
// Thread.sleep(1000)
|
||||||
|
// } catch (e: InterruptedException) {
|
||||||
|
// e.printStackTrace()
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// }.start()
|
||||||
|
// }
|
||||||
|
|
||||||
|
private fun updateUiWithUser(model: LoggedInUserView) {
|
||||||
|
val welcome = getString(R.string.welcome)
|
||||||
|
val displayName = model.displayName
|
||||||
|
// TODO : initiate successful logged in experience
|
||||||
|
Toast.makeText(
|
||||||
|
applicationContext,
|
||||||
|
"$welcome $displayName",
|
||||||
|
Toast.LENGTH_LONG
|
||||||
|
).show()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun showLoginFailed(@StringRes errorString: Int) {
|
||||||
|
Toast.makeText(applicationContext, errorString, Toast.LENGTH_SHORT).show()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Extension function to simplify setting an afterTextChanged action to EditText components.
|
||||||
|
*/
|
||||||
|
fun EditText.afterTextChanged(afterTextChanged: (String) -> Unit) {
|
||||||
|
this.addTextChangedListener(object : TextWatcher {
|
||||||
|
override fun afterTextChanged(editable: Editable?) {
|
||||||
|
afterTextChanged.invoke(editable.toString())
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) {}
|
||||||
|
|
||||||
|
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {}
|
||||||
|
})
|
||||||
|
}
|
10
app/app/src/main/java/cn/openp2p/ui/login/LoginFormState.kt
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package cn.openp2p.ui.login
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data validation state of the login form.
|
||||||
|
*/
|
||||||
|
data class LoginFormState(
|
||||||
|
val usernameError: Int? = null,
|
||||||
|
val passwordError: Int? = null,
|
||||||
|
val isDataValid: Boolean = false
|
||||||
|
)
|
9
app/app/src/main/java/cn/openp2p/ui/login/LoginResult.kt
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
package cn.openp2p.ui.login
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Authentication result : success (user details) or error message.
|
||||||
|
*/
|
||||||
|
data class LoginResult(
|
||||||
|
val success: LoggedInUserView? = null,
|
||||||
|
val error: Int? = null
|
||||||
|
)
|
57
app/app/src/main/java/cn/openp2p/ui/login/LoginViewModel.kt
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
package cn.openp2p.ui.login
|
||||||
|
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import android.util.Patterns
|
||||||
|
import cn.openp2p.data.LoginRepository
|
||||||
|
import cn.openp2p.data.Result
|
||||||
|
|
||||||
|
import cn.openp2p.R
|
||||||
|
|
||||||
|
class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() {
|
||||||
|
|
||||||
|
private val _loginForm = MutableLiveData<LoginFormState>()
|
||||||
|
val loginFormState: LiveData<LoginFormState> = _loginForm
|
||||||
|
|
||||||
|
private val _loginResult = MutableLiveData<LoginResult>()
|
||||||
|
val loginResult: LiveData<LoginResult> = _loginResult
|
||||||
|
|
||||||
|
fun login(username: String, password: String) {
|
||||||
|
// can be launched in a separate asynchronous job
|
||||||
|
val result = loginRepository.login(username, password)
|
||||||
|
|
||||||
|
if (result is Result.Success) {
|
||||||
|
_loginResult.value =
|
||||||
|
LoginResult(success = LoggedInUserView(displayName = result.data.displayName))
|
||||||
|
} else {
|
||||||
|
_loginResult.value = LoginResult(error = R.string.login_failed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun loginDataChanged(username: String, password: String) {
|
||||||
|
// if (!isUserNameValid(username)) {
|
||||||
|
// _loginForm.value = LoginFormState(usernameError = R.string.invalid_username)
|
||||||
|
// } else
|
||||||
|
if (!isPasswordValid(password)) {
|
||||||
|
_loginForm.value = LoginFormState(passwordError = R.string.invalid_password)
|
||||||
|
} else {
|
||||||
|
_loginForm.value = LoginFormState(isDataValid = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A placeholder username validation check
|
||||||
|
private fun isUserNameValid(username: String): Boolean {
|
||||||
|
return true
|
||||||
|
return if (username.contains('@')) {
|
||||||
|
Patterns.EMAIL_ADDRESS.matcher(username).matches()
|
||||||
|
} else {
|
||||||
|
username.isNotBlank()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// A placeholder password validation check
|
||||||
|
private fun isPasswordValid(password: String): Boolean {
|
||||||
|
return password.length > 5
|
||||||
|
}
|
||||||
|
}
|
@@ -0,0 +1,25 @@
|
|||||||
|
package cn.openp2p.ui.login
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.ViewModelProvider
|
||||||
|
import cn.openp2p.data.LoginDataSource
|
||||||
|
import cn.openp2p.data.LoginRepository
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ViewModel provider factory to instantiate LoginViewModel.
|
||||||
|
* Required given LoginViewModel has a non-empty constructor
|
||||||
|
*/
|
||||||
|
class LoginViewModelFactory : ViewModelProvider.Factory {
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
override fun <T : ViewModel> create(modelClass: Class<T>): T {
|
||||||
|
if (modelClass.isAssignableFrom(LoginViewModel::class.java)) {
|
||||||
|
return LoginViewModel(
|
||||||
|
loginRepository = LoginRepository(
|
||||||
|
dataSource = LoginDataSource()
|
||||||
|
)
|
||||||
|
) as T
|
||||||
|
}
|
||||||
|
throw IllegalArgumentException("Unknown ViewModel class")
|
||||||
|
}
|
||||||
|
}
|
30
app/app/src/main/res/drawable-v24/ic_launcher_foreground.xml
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:aapt="http://schemas.android.com/aapt"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path android:pathData="M31,63.928c0,0 6.4,-11 12.1,-13.1c7.2,-2.6 26,-1.4 26,-1.4l38.1,38.1L107,108.928l-32,-1L31,63.928z">
|
||||||
|
<aapt:attr name="android:fillColor">
|
||||||
|
<gradient
|
||||||
|
android:endX="85.84757"
|
||||||
|
android:endY="92.4963"
|
||||||
|
android:startX="42.9492"
|
||||||
|
android:startY="49.59793"
|
||||||
|
android:type="linear">
|
||||||
|
<item
|
||||||
|
android:color="#44000000"
|
||||||
|
android:offset="0.0" />
|
||||||
|
<item
|
||||||
|
android:color="#00000000"
|
||||||
|
android:offset="1.0" />
|
||||||
|
</gradient>
|
||||||
|
</aapt:attr>
|
||||||
|
</path>
|
||||||
|
<path
|
||||||
|
android:fillColor="#FFFFFF"
|
||||||
|
android:fillType="nonZero"
|
||||||
|
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z"
|
||||||
|
android:strokeWidth="1"
|
||||||
|
android:strokeColor="#00000000" />
|
||||||
|
</vector>
|
170
app/app/src/main/res/drawable/ic_launcher_background.xml
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="108dp"
|
||||||
|
android:height="108dp"
|
||||||
|
android:viewportWidth="108"
|
||||||
|
android:viewportHeight="108">
|
||||||
|
<path
|
||||||
|
android:fillColor="#3DDC84"
|
||||||
|
android:pathData="M0,0h108v108h-108z" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M9,0L9,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,0L19,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,0L29,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,0L39,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,0L49,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,0L59,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,0L69,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,0L79,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M89,0L89,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M99,0L99,108"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,9L108,9"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,19L108,19"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,29L108,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,39L108,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,49L108,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,59L108,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,69L108,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,79L108,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,89L108,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M0,99L108,99"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,29L89,29"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,39L89,39"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,49L89,49"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,59L89,59"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,69L89,69"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M19,79L89,79"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M29,19L29,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M39,19L39,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M49,19L49,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M59,19L59,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M69,19L69,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
<path
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:pathData="M79,19L79,89"
|
||||||
|
android:strokeWidth="0.8"
|
||||||
|
android:strokeColor="#33FFFFFF" />
|
||||||
|
</vector>
|
87
app/app/src/main/res/layout/activity_login.xml
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:id="@+id/container"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:paddingLeft="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingTop="@dimen/activity_vertical_margin"
|
||||||
|
android:paddingRight="@dimen/activity_horizontal_margin"
|
||||||
|
android:paddingBottom="@dimen/activity_vertical_margin"
|
||||||
|
tools:context=".ui.login.LoginActivity">
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/token"
|
||||||
|
android:layout_width="225dp"
|
||||||
|
android:layout_height="46dp"
|
||||||
|
android:hint="Token"
|
||||||
|
android:imeActionLabel="@string/action_sign_in_short"
|
||||||
|
android:imeOptions="actionDone"
|
||||||
|
android:selectAllOnFocus="true"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
android:id="@+id/login"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="start"
|
||||||
|
android:layout_marginStart="24dp"
|
||||||
|
android:layout_marginLeft="24dp"
|
||||||
|
android:enabled="false"
|
||||||
|
android:text="@string/action_sign_in"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/token"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
tools:layout_editor_absoluteY="-2dp" />
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
<ProgressBar
|
||||||
|
android:id="@+id/loading"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_gravity="center"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginTop="64dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:layout_marginBottom="64dp"
|
||||||
|
android:visibility="gone"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintEnd_toEndOf="@+id/token"
|
||||||
|
app:layout_constraintStart_toStartOf="@+id/token"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintVertical_bias="0.3" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/openp2pLog"
|
||||||
|
android:layout_width="359dp"
|
||||||
|
android:layout_height="548dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="none"
|
||||||
|
android:textIsSelectable="true"
|
||||||
|
android:focusable="false"
|
||||||
|
android:text="Name"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/onlineState" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/profile"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="打开控制台查看Token"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/token" />
|
||||||
|
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/onlineState"
|
||||||
|
android:layout_width="113dp"
|
||||||
|
android:layout_height="45dp"
|
||||||
|
android:ems="10"
|
||||||
|
android:inputType="textPersonName"
|
||||||
|
android:text="未登录"
|
||||||
|
app:layout_constraintStart_toEndOf="@+id/profile"
|
||||||
|
app:layout_constraintTop_toBottomOf="@+id/login" />
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
5
app/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<background android:drawable="@drawable/ic_launcher_background" />
|
||||||
|
<foreground android:drawable="@drawable/ic_launcher_foreground" />
|
||||||
|
</adaptive-icon>
|
BIN
app/app/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
app/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 5.2 KiB |
BIN
app/app/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.6 KiB |
BIN
app/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
app/app/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
app/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
app/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.7 KiB |
BIN
app/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
app/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
BIN
app/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
Normal file
After Width: | Height: | Size: 16 KiB |
16
app/app/src/main/res/values-night/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_200</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/black</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
10
app/app/src/main/res/values/colors.xml
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<resources>
|
||||||
|
<color name="purple_200">#FFBB86FC</color>
|
||||||
|
<color name="purple_500">#FF6200EE</color>
|
||||||
|
<color name="purple_700">#FF3700B3</color>
|
||||||
|
<color name="teal_200">#FF03DAC5</color>
|
||||||
|
<color name="teal_700">#FF018786</color>
|
||||||
|
<color name="black">#FF000000</color>
|
||||||
|
<color name="white">#FFFFFFFF</color>
|
||||||
|
</resources>
|
5
app/app/src/main/res/values/dimens.xml
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<resources>
|
||||||
|
<!-- Default screen margins, per the Android Design guidelines. -->
|
||||||
|
<dimen name="activity_horizontal_margin">16dp</dimen>
|
||||||
|
<dimen name="activity_vertical_margin">16dp</dimen>
|
||||||
|
</resources>
|
36
app/app/src/main/res/values/strings.xml
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
<resources>
|
||||||
|
<string name="app_name">OpenP2P</string>
|
||||||
|
<!-- Strings related to login -->
|
||||||
|
<string name="prompt_email">Email</string>
|
||||||
|
<string name="prompt_password">Password</string>
|
||||||
|
<string name="action_sign_in">登录</string>
|
||||||
|
<string name="action_sign_in_short">Sign in</string>
|
||||||
|
<string name="welcome">"Welcome !"</string>
|
||||||
|
<string name="invalid_username">Not a valid username</string>
|
||||||
|
<string name="invalid_password">Token可以在 https://console.openp2p.cn/profile 获得</string>
|
||||||
|
<string name="login_failed">"Login failed"</string>
|
||||||
|
<string name="phone_setting">"安卓系统默认设置的”杀后台进程“会导致 OpenP2P 在后台运行一会后,被系统杀死进程,导致您的体验受到影响。您可以通过以下方式修改几个设置,解决此问题:
|
||||||
|
|
||||||
|
华为手机:
|
||||||
|
进入”设置“,搜索并进入“电池优化“界面,选中 OpenP2P 程序,不允许系统对其进行电池优化;
|
||||||
|
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”耗电情况“,开启”允许后台活动“即可;
|
||||||
|
|
||||||
|
小米手机:
|
||||||
|
进入”设置“,进入”更多应用“界面,选中 OpenP2P 程序,点击”省电策略“,设置为”无限制“;
|
||||||
|
进入”设置“,进入”特色功能“界面,选中”游戏加速“,将其关闭即可;
|
||||||
|
|
||||||
|
OPPO手机:
|
||||||
|
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”耗电保护“,关闭所有设置项即可;
|
||||||
|
|
||||||
|
VIVO手机:
|
||||||
|
进入”设置“,进入”电池“界面,点击”后台高耗电“,将 OpenP2P 设为允许高耗电时继续运行即可;
|
||||||
|
|
||||||
|
魅族手机:
|
||||||
|
进入”设置“,进入”应用管理“界面,选中 OpenP2P 程序,点击”权限管理“,点击”后台管理“,选择”允许后台运行“即可;
|
||||||
|
|
||||||
|
一加手机:
|
||||||
|
进入”设置“,进入”电池“界面,点击”电池优化“,将 OpenP2P 设为”不优化“即可;
|
||||||
|
|
||||||
|
三星手机:
|
||||||
|
进入”智能管理器“,进入”应用程序管理“界面,点击”管理自动运行“,将 OpenP2P 设为”允许后台运行“即可;"</string>
|
||||||
|
</resources>
|
16
app/app/src/main/res/values/themes.xml
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
<!-- Base application theme. -->
|
||||||
|
<style name="Theme.OpenP2P" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
|
<!-- Primary brand color. -->
|
||||||
|
<item name="colorPrimary">@color/purple_500</item>
|
||||||
|
<item name="colorPrimaryVariant">@color/purple_700</item>
|
||||||
|
<item name="colorOnPrimary">@color/white</item>
|
||||||
|
<!-- Secondary brand color. -->
|
||||||
|
<item name="colorSecondary">@color/teal_200</item>
|
||||||
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
|
<!-- Status bar color. -->
|
||||||
|
<item name="android:statusBarColor" tools:targetApi="l">?attr/colorPrimaryVariant</item>
|
||||||
|
<!-- Customize your theme here. -->
|
||||||
|
</style>
|
||||||
|
</resources>
|
19
app/gradle.properties
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# Project-wide Gradle settings.
|
||||||
|
# IDE (e.g. Android Studio) users:
|
||||||
|
# Gradle settings configured through the IDE *will override*
|
||||||
|
# any settings specified in this file.
|
||||||
|
# For more details on how to configure your build environment visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||||
|
# Specifies the JVM arguments used for the daemon process.
|
||||||
|
# The setting is particularly useful for tweaking memory settings.
|
||||||
|
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
|
||||||
|
# When configured, Gradle will run in incubating parallel mode.
|
||||||
|
# This option should only be used with decoupled projects. More details, visit
|
||||||
|
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||||
|
# org.gradle.parallel=true
|
||||||
|
# AndroidX package structure to make it clearer which packages are bundled with the
|
||||||
|
# Android operating system, and which are packaged with your app"s APK
|
||||||
|
# https://developer.android.com/topic/libraries/support-library/androidx-rn
|
||||||
|
android.useAndroidX=true
|
||||||
|
# Kotlin code style for this project: "official" or "obsolete":
|
||||||
|
kotlin.code.style=official
|
BIN
app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
6
app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
#Sat Oct 22 21:46:24 CST 2022
|
||||||
|
distributionBase=GRADLE_USER_HOME
|
||||||
|
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-bin.zip
|
||||||
|
distributionPath=wrapper/dists
|
||||||
|
zipStorePath=wrapper/dists
|
||||||
|
zipStoreBase=GRADLE_USER_HOME
|
172
app/gradlew
vendored
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
#!/usr/bin/env sh
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
##
|
||||||
|
## Gradle start up script for UN*X
|
||||||
|
##
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
# Attempt to set APP_HOME
|
||||||
|
# Resolve links: $0 may be a link
|
||||||
|
PRG="$0"
|
||||||
|
# Need this for relative symlinks.
|
||||||
|
while [ -h "$PRG" ] ; do
|
||||||
|
ls=`ls -ld "$PRG"`
|
||||||
|
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||||
|
if expr "$link" : '/.*' > /dev/null; then
|
||||||
|
PRG="$link"
|
||||||
|
else
|
||||||
|
PRG=`dirname "$PRG"`"/$link"
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
SAVED="`pwd`"
|
||||||
|
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||||
|
APP_HOME="`pwd -P`"
|
||||||
|
cd "$SAVED" >/dev/null
|
||||||
|
|
||||||
|
APP_NAME="Gradle"
|
||||||
|
APP_BASE_NAME=`basename "$0"`
|
||||||
|
|
||||||
|
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
DEFAULT_JVM_OPTS=""
|
||||||
|
|
||||||
|
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||||
|
MAX_FD="maximum"
|
||||||
|
|
||||||
|
warn () {
|
||||||
|
echo "$*"
|
||||||
|
}
|
||||||
|
|
||||||
|
die () {
|
||||||
|
echo
|
||||||
|
echo "$*"
|
||||||
|
echo
|
||||||
|
exit 1
|
||||||
|
}
|
||||||
|
|
||||||
|
# OS specific support (must be 'true' or 'false').
|
||||||
|
cygwin=false
|
||||||
|
msys=false
|
||||||
|
darwin=false
|
||||||
|
nonstop=false
|
||||||
|
case "`uname`" in
|
||||||
|
CYGWIN* )
|
||||||
|
cygwin=true
|
||||||
|
;;
|
||||||
|
Darwin* )
|
||||||
|
darwin=true
|
||||||
|
;;
|
||||||
|
MINGW* )
|
||||||
|
msys=true
|
||||||
|
;;
|
||||||
|
NONSTOP* )
|
||||||
|
nonstop=true
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||||
|
|
||||||
|
# Determine the Java command to use to start the JVM.
|
||||||
|
if [ -n "$JAVA_HOME" ] ; then
|
||||||
|
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||||
|
# IBM's JDK on AIX uses strange locations for the executables
|
||||||
|
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||||
|
else
|
||||||
|
JAVACMD="$JAVA_HOME/bin/java"
|
||||||
|
fi
|
||||||
|
if [ ! -x "$JAVACMD" ] ; then
|
||||||
|
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
JAVACMD="java"
|
||||||
|
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
|
||||||
|
Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
location of your Java installation."
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Increase the maximum file descriptors if we can.
|
||||||
|
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
|
||||||
|
MAX_FD_LIMIT=`ulimit -H -n`
|
||||||
|
if [ $? -eq 0 ] ; then
|
||||||
|
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||||
|
MAX_FD="$MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
ulimit -n $MAX_FD
|
||||||
|
if [ $? -ne 0 ] ; then
|
||||||
|
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Darwin, add options to specify how the application appears in the dock
|
||||||
|
if $darwin; then
|
||||||
|
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||||
|
fi
|
||||||
|
|
||||||
|
# For Cygwin, switch paths to Windows format before running java
|
||||||
|
if $cygwin ; then
|
||||||
|
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||||
|
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||||
|
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||||
|
|
||||||
|
# We build the pattern for arguments to be converted via cygpath
|
||||||
|
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||||
|
SEP=""
|
||||||
|
for dir in $ROOTDIRSRAW ; do
|
||||||
|
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||||
|
SEP="|"
|
||||||
|
done
|
||||||
|
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||||
|
# Add a user-defined pattern to the cygpath arguments
|
||||||
|
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||||
|
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||||
|
fi
|
||||||
|
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||||
|
i=0
|
||||||
|
for arg in "$@" ; do
|
||||||
|
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||||
|
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||||
|
|
||||||
|
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||||
|
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||||
|
else
|
||||||
|
eval `echo args$i`="\"$arg\""
|
||||||
|
fi
|
||||||
|
i=$((i+1))
|
||||||
|
done
|
||||||
|
case $i in
|
||||||
|
(0) set -- ;;
|
||||||
|
(1) set -- "$args0" ;;
|
||||||
|
(2) set -- "$args0" "$args1" ;;
|
||||||
|
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||||
|
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||||
|
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||||
|
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||||
|
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||||
|
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||||
|
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||||
|
esac
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Escape application args
|
||||||
|
save () {
|
||||||
|
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
|
||||||
|
echo " "
|
||||||
|
}
|
||||||
|
APP_ARGS=$(save "$@")
|
||||||
|
|
||||||
|
# Collect all arguments for the java command, following the shell quoting and substitution rules
|
||||||
|
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
|
||||||
|
|
||||||
|
# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
|
||||||
|
if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
|
||||||
|
cd "$(dirname "$0")"
|
||||||
|
fi
|
||||||
|
|
||||||
|
exec "$JAVACMD" "$@"
|
84
app/gradlew.bat
vendored
Normal file
@@ -0,0 +1,84 @@
|
|||||||
|
@if "%DEBUG%" == "" @echo off
|
||||||
|
@rem ##########################################################################
|
||||||
|
@rem
|
||||||
|
@rem Gradle startup script for Windows
|
||||||
|
@rem
|
||||||
|
@rem ##########################################################################
|
||||||
|
|
||||||
|
@rem Set local scope for the variables with windows NT shell
|
||||||
|
if "%OS%"=="Windows_NT" setlocal
|
||||||
|
|
||||||
|
set DIRNAME=%~dp0
|
||||||
|
if "%DIRNAME%" == "" set DIRNAME=.
|
||||||
|
set APP_BASE_NAME=%~n0
|
||||||
|
set APP_HOME=%DIRNAME%
|
||||||
|
|
||||||
|
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||||
|
set DEFAULT_JVM_OPTS=
|
||||||
|
|
||||||
|
@rem Find java.exe
|
||||||
|
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||||
|
|
||||||
|
set JAVA_EXE=java.exe
|
||||||
|
%JAVA_EXE% -version >NUL 2>&1
|
||||||
|
if "%ERRORLEVEL%" == "0" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:findJavaFromJavaHome
|
||||||
|
set JAVA_HOME=%JAVA_HOME:"=%
|
||||||
|
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||||
|
|
||||||
|
if exist "%JAVA_EXE%" goto init
|
||||||
|
|
||||||
|
echo.
|
||||||
|
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||||
|
echo.
|
||||||
|
echo Please set the JAVA_HOME variable in your environment to match the
|
||||||
|
echo location of your Java installation.
|
||||||
|
|
||||||
|
goto fail
|
||||||
|
|
||||||
|
:init
|
||||||
|
@rem Get command-line arguments, handling Windows variants
|
||||||
|
|
||||||
|
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||||
|
|
||||||
|
:win9xME_args
|
||||||
|
@rem Slurp the command line arguments.
|
||||||
|
set CMD_LINE_ARGS=
|
||||||
|
set _SKIP=2
|
||||||
|
|
||||||
|
:win9xME_args_slurp
|
||||||
|
if "x%~1" == "x" goto execute
|
||||||
|
|
||||||
|
set CMD_LINE_ARGS=%*
|
||||||
|
|
||||||
|
:execute
|
||||||
|
@rem Setup the command line
|
||||||
|
|
||||||
|
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||||
|
|
||||||
|
@rem Execute Gradle
|
||||||
|
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||||
|
|
||||||
|
:end
|
||||||
|
@rem End local scope for the variables with windows NT shell
|
||||||
|
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||||
|
|
||||||
|
:fail
|
||||||
|
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||||
|
rem the _cmd.exe /c_ return code!
|
||||||
|
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||||
|
exit /b 1
|
||||||
|
|
||||||
|
:mainEnd
|
||||||
|
if "%OS%"=="Windows_NT" endlocal
|
||||||
|
|
||||||
|
:omega
|
BIN
app/openp2p.jks
Normal file
2
app/settings.gradle
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
rootProject.name = "OpenP2P"
|
||||||
|
include ':app'
|
7
cmd/openp2p.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import openp2p "openp2p/core"
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
openp2p.Run()
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -127,14 +127,14 @@ func netInfo() *NetInfo {
|
|||||||
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
|
client := &http.Client{Transport: tr, Timeout: time.Second * 10}
|
||||||
r, err := client.Get("https://ifconfig.co/json")
|
r, err := client.Get("https://ifconfig.co/json")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gLog.Println(LvINFO, "netInfo error:", err)
|
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
buf := make([]byte, 1024*64)
|
buf := make([]byte, 1024*64)
|
||||||
n, err := r.Body.Read(buf)
|
n, err := r.Body.Read(buf)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gLog.Println(LvINFO, "netInfo error:", err)
|
gLog.Println(LvDEBUG, "netInfo error:", err)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
rsp := NetInfo{}
|
rsp := NetInfo{}
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
187
core/nat.go
Normal file
@@ -0,0 +1,187 @@
|
|||||||
|
package openp2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"math/rand"
|
||||||
|
"net"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
reuse "github.com/openp2p-cn/go-reuseport"
|
||||||
|
)
|
||||||
|
|
||||||
|
func natTCP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int) {
|
||||||
|
// dialer := &net.Dialer{
|
||||||
|
// LocalAddr: &net.TCPAddr{
|
||||||
|
// IP: net.ParseIP("0.0.0.0"),
|
||||||
|
// Port: localPort,
|
||||||
|
// },
|
||||||
|
// }
|
||||||
|
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", localPort), fmt.Sprintf("%s:%d", serverHost, serverPort), time.Second*5)
|
||||||
|
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
_, wrerr := conn.Write([]byte("1"))
|
||||||
|
if wrerr != nil {
|
||||||
|
fmt.Printf("Write error: %s\n", wrerr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
b := make([]byte, 1000)
|
||||||
|
conn.SetReadDeadline(time.Now().Add(time.Second * 5))
|
||||||
|
n, rderr := conn.Read(b)
|
||||||
|
if rderr != nil {
|
||||||
|
fmt.Printf("Read error: %s\n", rderr)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
arr := strings.Split(string(b[:n]), ":")
|
||||||
|
if len(arr) < 2 {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
publicIP = arr[0]
|
||||||
|
port, _ := strconv.ParseInt(arr[1], 10, 32)
|
||||||
|
publicPort = int(port)
|
||||||
|
return
|
||||||
|
|
||||||
|
}
|
||||||
|
func natTest(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int, err error) {
|
||||||
|
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
|
||||||
|
if err != nil {
|
||||||
|
gLog.Println(LvERROR, "natTest listen udp error:", err)
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
|
||||||
|
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// The connection can write data to the desired address.
|
||||||
|
msg, err := newMessage(MsgNATDetect, 0, nil)
|
||||||
|
_, err = conn.WriteTo(msg, dst)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
deadline := time.Now().Add(NatTestTimeout)
|
||||||
|
err = conn.SetReadDeadline(deadline)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
buffer := make([]byte, 1024)
|
||||||
|
nRead, _, err := conn.ReadFrom(buffer)
|
||||||
|
if err != nil {
|
||||||
|
gLog.Println(LvERROR, "NAT detect error:", err)
|
||||||
|
return "", 0, err
|
||||||
|
}
|
||||||
|
natRsp := NatDetectRsp{}
|
||||||
|
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
|
||||||
|
|
||||||
|
return natRsp.IP, natRsp.Port, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, hasIPvr int, hasUPNPorNATPMP int, err error) {
|
||||||
|
// the random local port may be used by other.
|
||||||
|
localPort := int(rand.Uint32()%15000 + 50000)
|
||||||
|
echoPort := P2PNetworkInstance(nil).config.TCPPort
|
||||||
|
ip1, port1, err := natTest(host, udp1, localPort)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, 0, 0, err
|
||||||
|
}
|
||||||
|
hasIPv4, hasUPNPorNATPMP := publicIPTest(ip1, echoPort)
|
||||||
|
gLog.Printf(LvINFO, "local port:%d, nat port:%d, hasIPv4:%d, UPNP:%d", localPort, port1, hasIPv4, hasUPNPorNATPMP)
|
||||||
|
_, port2, err := natTest(host, udp2, localPort) // 2rd nat test not need testing publicip
|
||||||
|
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port2)
|
||||||
|
if err != nil {
|
||||||
|
return "", 0, hasIPv4, hasUPNPorNATPMP, err
|
||||||
|
}
|
||||||
|
natType := NATSymmetric
|
||||||
|
if port1 == port2 {
|
||||||
|
natType = NATCone
|
||||||
|
}
|
||||||
|
return ip1, natType, hasIPv4, hasUPNPorNATPMP, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func publicIPTest(publicIP string, echoPort int) (hasPublicIP int, hasUPNPorNATPMP int) {
|
||||||
|
var echoConn *net.UDPConn
|
||||||
|
var wg sync.WaitGroup
|
||||||
|
wg.Add(1)
|
||||||
|
go func() {
|
||||||
|
var err error
|
||||||
|
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
|
||||||
|
if err != nil {
|
||||||
|
gLog.Println(LvERROR, "echo server listen error:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
buf := make([]byte, 1600)
|
||||||
|
// close outside for breaking the ReadFromUDP
|
||||||
|
// wait 5s for echo testing
|
||||||
|
wg.Done()
|
||||||
|
echoConn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
||||||
|
n, addr, err := echoConn.ReadFromUDP(buf)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
echoConn.WriteToUDP(buf[0:n], addr)
|
||||||
|
}()
|
||||||
|
wg.Wait() // wait echo udp
|
||||||
|
defer echoConn.Close()
|
||||||
|
// testing for public ip
|
||||||
|
for i := 0; i < 2; i++ {
|
||||||
|
if i == 1 {
|
||||||
|
// test upnp or nat-pmp
|
||||||
|
nat, err := Discover()
|
||||||
|
if err != nil || nat == nil {
|
||||||
|
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
ext, err := nat.GetExternalAddress()
|
||||||
|
if err != nil {
|
||||||
|
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
log.Println("PublicIP:", ext)
|
||||||
|
|
||||||
|
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30)
|
||||||
|
if err != nil {
|
||||||
|
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gLog.Printf(LvDEBUG, "public ip test start %s:%d", publicIP, echoPort)
|
||||||
|
conn, err := net.ListenUDP("udp", nil)
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", publicIP, echoPort))
|
||||||
|
if err != nil {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
conn.WriteTo([]byte("echo"), dst)
|
||||||
|
buf := make([]byte, 1600)
|
||||||
|
|
||||||
|
// wait for echo testing
|
||||||
|
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
||||||
|
_, _, err = conn.ReadFromUDP(buf)
|
||||||
|
if err == nil {
|
||||||
|
if i == 1 {
|
||||||
|
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
||||||
|
hasUPNPorNATPMP = 1
|
||||||
|
} else {
|
||||||
|
gLog.Println(LvDEBUG, "public ip:YES")
|
||||||
|
hasPublicIP = 1
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
97
core/openp2p.go
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
package openp2p
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"math/rand"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Run() {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
baseDir := filepath.Dir(os.Args[0])
|
||||||
|
os.Chdir(baseDir) // for system service
|
||||||
|
gLog = NewLogger(baseDir, ProducnName, LvDEBUG, 1024*1024, LogFileAndConsole)
|
||||||
|
// TODO: install sub command, deamon process
|
||||||
|
if len(os.Args) > 1 {
|
||||||
|
switch os.Args[1] {
|
||||||
|
case "version", "-v", "--version":
|
||||||
|
fmt.Println(OpenP2PVersion)
|
||||||
|
return
|
||||||
|
case "update":
|
||||||
|
gLog = NewLogger(baseDir, ProducnName, LvDEBUG, 1024*1024, LogFileAndConsole)
|
||||||
|
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
||||||
|
d := daemon{}
|
||||||
|
err := d.Control("restart", targetPath, nil)
|
||||||
|
if err != nil {
|
||||||
|
gLog.Println(LvERROR, "restart service error:", err)
|
||||||
|
} else {
|
||||||
|
gLog.Println(LvINFO, "restart service ok.")
|
||||||
|
}
|
||||||
|
return
|
||||||
|
case "install":
|
||||||
|
install()
|
||||||
|
return
|
||||||
|
case "uninstall":
|
||||||
|
uninstall()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
installByFilename()
|
||||||
|
}
|
||||||
|
parseParams("")
|
||||||
|
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||||
|
gLog.Println(LvINFO, "Contact: QQ: 477503927, Email: openp2p.cn@gmail.com")
|
||||||
|
|
||||||
|
if gConf.daemonMode {
|
||||||
|
d := daemon{}
|
||||||
|
d.run()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
gLog.Println(LvINFO, &gConf)
|
||||||
|
setFirewall()
|
||||||
|
network := P2PNetworkInstance(&gConf.Network)
|
||||||
|
if ok := network.Connect(30000); !ok {
|
||||||
|
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
gLog.Println(LvINFO, "waiting for connection...")
|
||||||
|
forever := make(chan bool)
|
||||||
|
<-forever
|
||||||
|
}
|
||||||
|
|
||||||
|
var network *P2PNetwork
|
||||||
|
|
||||||
|
// for Android app
|
||||||
|
// gomobile not support uint64 exported to java
|
||||||
|
func RunAsModule(baseDir string, token string, bw int) *P2PNetwork {
|
||||||
|
rand.Seed(time.Now().UnixNano())
|
||||||
|
os.Chdir(baseDir) // for system service
|
||||||
|
gLog = NewLogger(baseDir, ProducnName, LvDEBUG, 1024*1024, LogFileAndConsole)
|
||||||
|
// TODO: install sub command, deamon process
|
||||||
|
parseParams("")
|
||||||
|
|
||||||
|
n, _ := strconv.ParseUint(token, 10, 64)
|
||||||
|
gConf.setToken(n)
|
||||||
|
gConf.setShareBandwidth(0)
|
||||||
|
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
||||||
|
gLog.Println(LvINFO, "Contact: QQ: 477503927, Email: openp2p.cn@gmail.com")
|
||||||
|
gLog.Println(LvINFO, &gConf)
|
||||||
|
|
||||||
|
network = P2PNetworkInstance(&gConf.Network)
|
||||||
|
if ok := network.Connect(30000); !ok {
|
||||||
|
gLog.Println(LvERROR, "P2PNetwork login error")
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
gLog.Println(LvINFO, "waiting for connection...")
|
||||||
|
return network
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetToken(baseDir string) string {
|
||||||
|
os.Chdir(baseDir)
|
||||||
|
gConf.load()
|
||||||
|
return fmt.Sprintf("%d", gConf.Network.Token)
|
||||||
|
}
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"sync"
|
"sync"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -32,6 +32,7 @@ type P2PNetwork struct {
|
|||||||
writeMtx sync.Mutex
|
writeMtx sync.Mutex
|
||||||
serverTs int64
|
serverTs int64
|
||||||
localTs int64
|
localTs int64
|
||||||
|
hbTime time.Time
|
||||||
// msgMap sync.Map
|
// msgMap sync.Map
|
||||||
msgMap map[uint64]chan []byte //key: nodeID
|
msgMap map[uint64]chan []byte //key: nodeID
|
||||||
msgMapMtx sync.Mutex
|
msgMapMtx sync.Mutex
|
||||||
@@ -85,7 +86,7 @@ func (pn *P2PNetwork) run() {
|
|||||||
func (pn *P2PNetwork) Connect(timeout int) bool {
|
func (pn *P2PNetwork) Connect(timeout int) bool {
|
||||||
// waiting for login response
|
// waiting for login response
|
||||||
for i := 0; i < (timeout / 1000); i++ {
|
for i := 0; i < (timeout / 1000); i++ {
|
||||||
if pn.serverTs != 0 {
|
if pn.hbTime.After(time.Now().Add(-NetworkHeartbeatTime)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
@@ -109,12 +110,10 @@ func (pn *P2PNetwork) runAll() {
|
|||||||
config.AppName = fmt.Sprintf("%s%d", config.Protocol, config.SrcPort)
|
config.AppName = fmt.Sprintf("%s%d", config.Protocol, config.SrcPort)
|
||||||
}
|
}
|
||||||
appExist := false
|
appExist := false
|
||||||
var appID uint64
|
|
||||||
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
|
i, ok := pn.apps.Load(fmt.Sprintf("%s%d", config.Protocol, config.SrcPort))
|
||||||
if ok {
|
if ok {
|
||||||
app := i.(*p2pApp)
|
app := i.(*p2pApp)
|
||||||
appExist = true
|
appExist = true
|
||||||
appID = app.id
|
|
||||||
if app.isActive() {
|
if app.isActive() {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@@ -123,18 +122,21 @@ func (pn *P2PNetwork) runAll() {
|
|||||||
pn.DeleteApp(*config)
|
pn.DeleteApp(*config)
|
||||||
}
|
}
|
||||||
if config.retryNum > 0 {
|
if config.retryNum > 0 {
|
||||||
gLog.Printf(LvINFO, "detect app %s(%d) disconnect, reconnecting the %d times...", config.AppName, appID, config.retryNum)
|
gLog.Printf(LvINFO, "detect app %s disconnect, reconnecting the %d times...", config.AppName, config.retryNum)
|
||||||
if time.Now().Add(-time.Minute * 15).After(config.retryTime) { // normal lasts 15min
|
if time.Now().Add(-time.Minute * 15).After(config.retryTime) { // normal lasts 15min
|
||||||
config.retryNum = 0
|
config.retryNum = 0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
config.retryNum++
|
config.retryNum++
|
||||||
config.retryTime = time.Now()
|
config.retryTime = time.Now()
|
||||||
increase := math.Pow(1.3, float64(config.retryNum))
|
increase := math.Pow(1.5, float64(config.retryNum)) // exponential increase retry time. 1.5^x
|
||||||
if increase > 900 {
|
if increase > 900 {
|
||||||
increase = 900
|
increase = 900
|
||||||
|
config.Enabled = 0
|
||||||
|
gLog.Printf(LvWARN, "app %s has stopped retry, manually enable it on Web console", config.AppName)
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
config.nextRetryTime = time.Now().Add(time.Second * time.Duration(increase)) // exponential increase retry time. 1.3^x
|
config.nextRetryTime = time.Now().Add(time.Second * time.Duration(increase))
|
||||||
config.connectTime = time.Now()
|
config.connectTime = time.Now()
|
||||||
config.peerToken = pn.config.Token
|
config.peerToken = pn.config.Token
|
||||||
gConf.mtx.Unlock() // AddApp will take a period of time
|
gConf.mtx.Unlock() // AddApp will take a period of time
|
||||||
@@ -503,10 +505,12 @@ func (pn *P2PNetwork) init() error {
|
|||||||
if rsp != nil && rsp.Country != "" {
|
if rsp != nil && rsp.Country != "" {
|
||||||
if IsIPv6(rsp.IP.String()) {
|
if IsIPv6(rsp.IP.String()) {
|
||||||
pn.config.publicIPv6 = rsp.IP.String()
|
pn.config.publicIPv6 = rsp.IP.String()
|
||||||
req.IPv6 = rsp.IP.String()
|
|
||||||
}
|
}
|
||||||
req.NetInfo = *rsp
|
req.NetInfo = *rsp
|
||||||
|
} else {
|
||||||
|
pn.refreshIPv6(true)
|
||||||
}
|
}
|
||||||
|
req.IPv6 = pn.config.publicIPv6
|
||||||
pn.write(MsgReport, MsgReportBasic, &req)
|
pn.write(MsgReport, MsgReportBasic, &req)
|
||||||
gLog.Println(LvDEBUG, "P2PNetwork init ok")
|
gLog.Println(LvDEBUG, "P2PNetwork init ok")
|
||||||
break
|
break
|
||||||
@@ -540,6 +544,7 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
|
|||||||
pn.running = false
|
pn.running = false
|
||||||
} else {
|
} else {
|
||||||
pn.serverTs = rsp.Ts
|
pn.serverTs = rsp.Ts
|
||||||
|
pn.hbTime = time.Now()
|
||||||
pn.config.Token = rsp.Token
|
pn.config.Token = rsp.Token
|
||||||
pn.config.User = rsp.User
|
pn.config.User = rsp.User
|
||||||
gConf.setToken(rsp.Token)
|
gConf.setToken(rsp.Token)
|
||||||
@@ -553,6 +558,7 @@ func (pn *P2PNetwork) handleMessage(t int, msg []byte) {
|
|||||||
}
|
}
|
||||||
case MsgHeartbeat:
|
case MsgHeartbeat:
|
||||||
gLog.Printf(LvDEBUG, "P2PNetwork heartbeat ok")
|
gLog.Printf(LvDEBUG, "P2PNetwork heartbeat ok")
|
||||||
|
pn.hbTime = time.Now()
|
||||||
case MsgPush:
|
case MsgPush:
|
||||||
handlePush(pn, head.SubType, msg)
|
handlePush(pn, head.SubType, msg)
|
||||||
default:
|
default:
|
||||||
@@ -688,21 +694,21 @@ func (pn *P2PNetwork) updateAppHeartbeat(appID uint64) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pn *P2PNetwork) refreshIPv6() {
|
func (pn *P2PNetwork) refreshIPv6(force bool) {
|
||||||
if !IsIPv6(pn.config.publicIPv6) { // not support ipv6, not refresh
|
if !force && !IsIPv6(pn.config.publicIPv6) { // not support ipv6, not refresh
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
client := &http.Client{Timeout: time.Second * 10}
|
client := &http.Client{Timeout: time.Second * 10}
|
||||||
r, err := client.Get("http://6.ipw.cn")
|
r, err := client.Get("http://6.ipw.cn")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
gLog.Println(LvINFO, "refreshIPv6 error:", err)
|
gLog.Println(LvDEBUG, "refreshIPv6 error:", err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
defer r.Body.Close()
|
defer r.Body.Close()
|
||||||
buf := make([]byte, 1024)
|
buf := make([]byte, 1024)
|
||||||
n, err := r.Body.Read(buf)
|
n, err := r.Body.Read(buf)
|
||||||
if n <= 0 {
|
if n <= 0 {
|
||||||
gLog.Println(LvINFO, "netInfo error:", err, n)
|
gLog.Println(LvINFO, "refreshIPv6 error:", err, n)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
pn.config.publicIPv6 = string(buf[:n])
|
pn.config.publicIPv6 = string(buf[:n])
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -67,7 +67,7 @@ func (t *P2PTunnel) initPort() {
|
|||||||
t.hbTimeRelay = time.Now().Add(time.Second * 600) // TODO: test fake time
|
t.hbTimeRelay = time.Now().Add(time.Second * 600) // TODO: test fake time
|
||||||
localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param
|
localPort := int(rand.Uint32()%15000 + 50000) // if the process has bug, will add many upnp port. use specify p2p port by param
|
||||||
if t.config.linkMode == LinkModeTCP6 {
|
if t.config.linkMode == LinkModeTCP6 {
|
||||||
t.pn.refreshIPv6()
|
t.pn.refreshIPv6(false)
|
||||||
}
|
}
|
||||||
if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 {
|
if t.config.linkMode == LinkModeTCP6 || t.config.linkMode == LinkModeTCP4 {
|
||||||
t.coneLocalPort = t.pn.config.TCPPort
|
t.coneLocalPort = t.pn.config.TCPPort
|
||||||
@@ -75,7 +75,7 @@ func (t *P2PTunnel) initPort() {
|
|||||||
}
|
}
|
||||||
if t.config.linkMode == LinkModeUDPPunch {
|
if t.config.linkMode == LinkModeUDPPunch {
|
||||||
// prepare one random cone hole
|
// prepare one random cone hole
|
||||||
_, _, _, natPort, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort, 0)
|
_, natPort, _ := natTest(t.pn.config.ServerHost, t.pn.config.UDPPort1, localPort)
|
||||||
t.coneLocalPort = localPort
|
t.coneLocalPort = localPort
|
||||||
t.coneNatPort = natPort
|
t.coneNatPort = natPort
|
||||||
}
|
}
|
||||||
@@ -593,7 +593,7 @@ func (t *P2PTunnel) listen() error {
|
|||||||
}
|
}
|
||||||
// only private node set ipv6
|
// only private node set ipv6
|
||||||
if t.config.fromToken == t.pn.config.Token {
|
if t.config.fromToken == t.pn.config.Token {
|
||||||
t.pn.refreshIPv6()
|
t.pn.refreshIPv6(false)
|
||||||
rsp.IPv6 = t.pn.config.publicIPv6
|
rsp.IPv6 = t.pn.config.publicIPv6
|
||||||
}
|
}
|
||||||
|
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
@@ -10,7 +10,7 @@ import (
|
|||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
const OpenP2PVersion = "3.4.0"
|
const OpenP2PVersion = "3.5.0"
|
||||||
const ProducnName string = "openp2p"
|
const ProducnName string = "openp2p"
|
||||||
const LeastSupportVersion = "3.0.0"
|
const LeastSupportVersion = "3.0.0"
|
||||||
|
|
@@ -1,5 +1,5 @@
|
|||||||
// Time-based One-time Password
|
// Time-based One-time Password
|
||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"crypto/hmac"
|
"crypto/hmac"
|
@@ -1,5 +1,5 @@
|
|||||||
// Time-based One-time Password
|
// Time-based One-time Password
|
||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"time"
|
"time"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"archive/tar"
|
"archive/tar"
|
@@ -1,7 +1,7 @@
|
|||||||
/*
|
/*
|
||||||
Taken from taipei-torrent And fix some bugs
|
Taken from taipei-torrent And fix some bugs
|
||||||
*/
|
*/
|
||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"strings"
|
"strings"
|
@@ -1,10 +1,11 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
"bytes"
|
"bytes"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"os"
|
"os"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
)
|
)
|
||||||
@@ -15,6 +16,9 @@ const (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func getOsName() (osName string) {
|
func getOsName() (osName string) {
|
||||||
|
if runtime.GOOS == "android" {
|
||||||
|
return "Android"
|
||||||
|
}
|
||||||
var sysnamePath string
|
var sysnamePath string
|
||||||
sysnamePath = "/etc/redhat-release"
|
sysnamePath = "/etc/redhat-release"
|
||||||
_, err := os.Stat(sysnamePath)
|
_, err := os.Stat(sysnamePath)
|
@@ -1,4 +1,4 @@
|
|||||||
package main
|
package openp2p
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
11
go.mod
@@ -7,7 +7,7 @@ require (
|
|||||||
github.com/kardianos/service v1.2.0
|
github.com/kardianos/service v1.2.0
|
||||||
github.com/lucas-clemente/quic-go v0.27.0
|
github.com/lucas-clemente/quic-go v0.27.0
|
||||||
github.com/openp2p-cn/go-reuseport v0.3.2
|
github.com/openp2p-cn/go-reuseport v0.3.2
|
||||||
golang.org/x/sys v0.0.0-20220422013727-9388b58f7150
|
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f
|
||||||
)
|
)
|
||||||
|
|
||||||
require (
|
require (
|
||||||
@@ -19,10 +19,9 @@ require (
|
|||||||
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
github.com/marten-seemann/qtls-go1-18 v0.1.1 // indirect
|
||||||
github.com/nxadm/tail v1.4.8 // indirect
|
github.com/nxadm/tail v1.4.8 // indirect
|
||||||
github.com/onsi/ginkgo v1.16.4 // indirect
|
github.com/onsi/ginkgo v1.16.4 // indirect
|
||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9 // indirect
|
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519 // indirect
|
||||||
golang.org/x/mod v0.4.2 // indirect
|
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
|
||||||
golang.org/x/net v0.0.0-20210428140749-89ef3d95e781 // indirect
|
golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect
|
||||||
golang.org/x/tools v0.1.1 // indirect
|
golang.org/x/tools v0.1.12 // indirect
|
||||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
|
||||||
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 // indirect
|
||||||
)
|
)
|
||||||
|
193
nat.go
@@ -1,193 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
"log"
|
|
||||||
"math/rand"
|
|
||||||
"net"
|
|
||||||
"strconv"
|
|
||||||
"strings"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
reuse "github.com/openp2p-cn/go-reuseport"
|
|
||||||
)
|
|
||||||
|
|
||||||
var echoConn *net.UDPConn
|
|
||||||
|
|
||||||
func natTCP(serverHost string, serverPort int, localPort int) (publicIP string, publicPort int) {
|
|
||||||
// dialer := &net.Dialer{
|
|
||||||
// LocalAddr: &net.TCPAddr{
|
|
||||||
// IP: net.ParseIP("0.0.0.0"),
|
|
||||||
// Port: localPort,
|
|
||||||
// },
|
|
||||||
// }
|
|
||||||
conn, err := reuse.DialTimeout("tcp4", fmt.Sprintf("%s:%d", "0.0.0.0", localPort), fmt.Sprintf("%s:%d", serverHost, serverPort), time.Second*5)
|
|
||||||
// conn, err := net.Dial("tcp4", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
|
||||||
if err != nil {
|
|
||||||
fmt.Printf("Dial tcp4 %s:%d error:%s", serverHost, serverPort, err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
_, wrerr := conn.Write([]byte("1"))
|
|
||||||
if wrerr != nil {
|
|
||||||
fmt.Printf("Write error: %s\n", wrerr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
b := make([]byte, 1000)
|
|
||||||
conn.SetReadDeadline(time.Now().Add(time.Second * 5))
|
|
||||||
n, rderr := conn.Read(b)
|
|
||||||
if rderr != nil {
|
|
||||||
fmt.Printf("Read error: %s\n", rderr)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
arr := strings.Split(string(b[:n]), ":")
|
|
||||||
if len(arr) < 2 {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
publicIP = arr[0]
|
|
||||||
port, _ := strconv.ParseInt(arr[1], 10, 32)
|
|
||||||
publicPort = int(port)
|
|
||||||
return
|
|
||||||
|
|
||||||
}
|
|
||||||
func natTest(serverHost string, serverPort int, localPort int, echoPort int) (publicIP string, hasPublicIP int, hasUPNPorNATPMP int, publicPort int, err error) {
|
|
||||||
conn, err := net.ListenPacket("udp", fmt.Sprintf(":%d", localPort))
|
|
||||||
if err != nil {
|
|
||||||
gLog.Println(LvERROR, "natTest listen udp error:", err)
|
|
||||||
return "", 0, 0, 0, err
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
|
|
||||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", serverHost, serverPort))
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, 0, 0, err
|
|
||||||
}
|
|
||||||
|
|
||||||
// The connection can write data to the desired address.
|
|
||||||
msg, err := newMessage(MsgNATDetect, 0, &NatDetectReq{SrcPort: localPort, EchoPort: echoPort})
|
|
||||||
_, err = conn.WriteTo(msg, dst)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, 0, 0, err
|
|
||||||
}
|
|
||||||
deadline := time.Now().Add(NatTestTimeout)
|
|
||||||
err = conn.SetReadDeadline(deadline)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, 0, 0, err
|
|
||||||
}
|
|
||||||
buffer := make([]byte, 1024)
|
|
||||||
nRead, _, err := conn.ReadFrom(buffer)
|
|
||||||
if err != nil {
|
|
||||||
gLog.Println(LvERROR, "NAT detect error:", err)
|
|
||||||
return "", 0, 0, 0, err
|
|
||||||
}
|
|
||||||
natRsp := NatDetectRsp{}
|
|
||||||
err = json.Unmarshal(buffer[openP2PHeaderSize:nRead], &natRsp)
|
|
||||||
hasPublicIP = 0
|
|
||||||
hasUPNPorNATPMP = 0
|
|
||||||
// testing for public ip
|
|
||||||
if echoPort != 0 {
|
|
||||||
for i := 0; i < 2; i++ {
|
|
||||||
if i == 1 {
|
|
||||||
// test upnp or nat-pmp
|
|
||||||
nat, err := Discover()
|
|
||||||
if err != nil || nat == nil {
|
|
||||||
gLog.Println(LvDEBUG, "could not perform UPNP discover:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
ext, err := nat.GetExternalAddress()
|
|
||||||
if err != nil {
|
|
||||||
gLog.Println(LvDEBUG, "could not perform UPNP external address:", err)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
log.Println("PublicIP:", ext)
|
|
||||||
|
|
||||||
externalPort, err := nat.AddPortMapping("udp", echoPort, echoPort, "openp2p", 30)
|
|
||||||
if err != nil {
|
|
||||||
gLog.Println(LvDEBUG, "could not add udp UPNP port mapping", externalPort)
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
nat.AddPortMapping("tcp", echoPort, echoPort, "openp2p", 604800)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
gLog.Printf(LvDEBUG, "public ip test start %s:%d", natRsp.IP, echoPort)
|
|
||||||
conn, err := net.ListenUDP("udp", nil)
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
defer conn.Close()
|
|
||||||
dst, err := net.ResolveUDPAddr("udp", fmt.Sprintf("%s:%d", natRsp.IP, echoPort))
|
|
||||||
if err != nil {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
conn.WriteTo([]byte("echo"), dst)
|
|
||||||
buf := make([]byte, 1600)
|
|
||||||
|
|
||||||
// wait for echo testing
|
|
||||||
conn.SetReadDeadline(time.Now().Add(PublicIPEchoTimeout))
|
|
||||||
_, _, err = conn.ReadFromUDP(buf)
|
|
||||||
if err == nil {
|
|
||||||
if i == 1 {
|
|
||||||
gLog.Println(LvDEBUG, "UPNP or NAT-PMP:YES")
|
|
||||||
hasUPNPorNATPMP = 1
|
|
||||||
} else {
|
|
||||||
gLog.Println(LvDEBUG, "public ip:YES")
|
|
||||||
hasPublicIP = 1
|
|
||||||
}
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return natRsp.IP, hasPublicIP, hasUPNPorNATPMP, natRsp.Port, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func getNATType(host string, udp1 int, udp2 int) (publicIP string, NATType int, hasIPvr int, hasUPNPorNATPMP int, err error) {
|
|
||||||
// the random local port may be used by other.
|
|
||||||
localPort := int(rand.Uint32()%15000 + 50000)
|
|
||||||
echoPort := P2PNetworkInstance(nil).config.TCPPort
|
|
||||||
go echo(echoPort)
|
|
||||||
// _, natPort := natTCP(host, 27181, localPort)
|
|
||||||
// gLog.Println(LvINFO, "nattcp:", natPort)
|
|
||||||
// _, natPort = natTCP(host, 27180, localPort)
|
|
||||||
// gLog.Println(LvINFO, "nattcp:", natPort)
|
|
||||||
ip1, hasIPv4, hasUPNPorNATPMP, port1, err := natTest(host, udp1, localPort, echoPort)
|
|
||||||
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port1)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, hasIPv4, hasUPNPorNATPMP, err
|
|
||||||
}
|
|
||||||
if echoConn != nil {
|
|
||||||
echoConn.Close()
|
|
||||||
echoConn = nil
|
|
||||||
}
|
|
||||||
// if hasPublicIP == 1 || hasUPNPorNATPMP == 1 {
|
|
||||||
// return ip1, NATNone, hasUPNPorNATPMP, nil
|
|
||||||
// }
|
|
||||||
_, _, _, port2, err := natTest(host, udp2, localPort, 0) // 2rd nat test not need testing publicip
|
|
||||||
gLog.Printf(LvDEBUG, "local port:%d nat port:%d", localPort, port2)
|
|
||||||
if err != nil {
|
|
||||||
return "", 0, hasIPv4, hasUPNPorNATPMP, err
|
|
||||||
}
|
|
||||||
natType := NATSymmetric
|
|
||||||
if port1 == port2 {
|
|
||||||
natType = NATCone
|
|
||||||
}
|
|
||||||
return ip1, natType, hasIPv4, hasUPNPorNATPMP, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func echo(echoPort int) {
|
|
||||||
var err error
|
|
||||||
echoConn, err = net.ListenUDP("udp", &net.UDPAddr{IP: net.IPv4zero, Port: echoPort})
|
|
||||||
if err != nil {
|
|
||||||
gLog.Println(LvERROR, "echo server listen error:", err)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
buf := make([]byte, 1600)
|
|
||||||
// close outside for breaking the ReadFromUDP
|
|
||||||
// wait 5s for echo testing
|
|
||||||
echoConn.SetReadDeadline(time.Now().Add(time.Second * 30))
|
|
||||||
n, addr, err := echoConn.ReadFromUDP(buf)
|
|
||||||
if err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
echoConn.WriteToUDP(buf[0:n], addr)
|
|
||||||
}
|
|
63
openp2p.go
@@ -1,63 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
|
||||||
"os"
|
|
||||||
"path/filepath"
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
binDir := filepath.Dir(os.Args[0])
|
|
||||||
os.Chdir(binDir) // for system service
|
|
||||||
gLog = NewLogger(binDir, "openp2p", LvDEBUG, 1024*1024, LogFileAndConsole)
|
|
||||||
// TODO: install sub command, deamon process
|
|
||||||
if len(os.Args) > 1 {
|
|
||||||
switch os.Args[1] {
|
|
||||||
case "version", "-v", "--version":
|
|
||||||
fmt.Println(OpenP2PVersion)
|
|
||||||
return
|
|
||||||
case "update":
|
|
||||||
gLog = NewLogger(filepath.Dir(os.Args[0]), "openp2p", LvDEBUG, 1024*1024, LogFileAndConsole)
|
|
||||||
targetPath := filepath.Join(defaultInstallPath, defaultBinName)
|
|
||||||
d := daemon{}
|
|
||||||
err := d.Control("restart", targetPath, nil)
|
|
||||||
if err != nil {
|
|
||||||
gLog.Println(LvERROR, "restart service error:", err)
|
|
||||||
} else {
|
|
||||||
gLog.Println(LvINFO, "restart service ok.")
|
|
||||||
}
|
|
||||||
return
|
|
||||||
case "install":
|
|
||||||
install()
|
|
||||||
return
|
|
||||||
case "uninstall":
|
|
||||||
uninstall()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
installByFilename()
|
|
||||||
}
|
|
||||||
parseParams("")
|
|
||||||
gLog.Println(LvINFO, "openp2p start. version: ", OpenP2PVersion)
|
|
||||||
gLog.Println(LvINFO, "Contact: QQ: 477503927, Email: openp2p.cn@gmail.com")
|
|
||||||
|
|
||||||
if gConf.daemonMode {
|
|
||||||
d := daemon{}
|
|
||||||
d.run()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
gLog.Println(LvINFO, &gConf)
|
|
||||||
setFirewall()
|
|
||||||
network := P2PNetworkInstance(&gConf.Network)
|
|
||||||
if ok := network.Connect(30000); !ok {
|
|
||||||
gLog.Println(LvERROR, "P2PNetwork login error")
|
|
||||||
return
|
|
||||||
}
|
|
||||||
gLog.Println(LvINFO, "waiting for connection...")
|
|
||||||
forever := make(chan bool)
|
|
||||||
<-forever
|
|
||||||
}
|
|
17
p2pconn.go
@@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
)
|
|
||||||
|
|
||||||
type p2pConn interface {
|
|
||||||
ReadMessage() (*openP2PHeader, []byte, error)
|
|
||||||
WriteBytes(uint16, uint16, []byte) error
|
|
||||||
WriteBuffer([]byte) error
|
|
||||||
WriteMessage(uint16, uint16, interface{}) error
|
|
||||||
Close() error
|
|
||||||
Accept() error
|
|
||||||
CloseListener()
|
|
||||||
SetReadDeadline(t time.Time) error
|
|
||||||
SetWriteDeadline(t time.Time) error
|
|
||||||
}
|
|