TEAC P90SD 更换电池

用了4年的P90SD,电池终于死的不能再死了。TB上买了安桥HA300的兼容电池(两机型基本就是运放不同)。

参考使用说明书和这篇拆解

  1. 侧边外壳是4颗1.3mm的六角螺丝。
  2. OLED上的玻璃是胶水粘的。需要加热,或者真空吸盘+翘板。
  3. 兼容电池比原装略厚。

最终成功复活。然而我再不想折腾第二次了…

Advertisements
Posted in Entertainment | Leave a comment

SpringBoot + GitHub + Azure Pipeline + Azure Web App

I still admire the ease to deploy Heroku with Java. But since I got some credit on Azure, let me try to make a similar workflow.

I would assume one have an Azure account.

First step, as usual, create a SpringBoot web app. Note the final target might need to be named as app.jar to be picked by Azure without configure.

One can put the code on GitHub or BitBucket or Azure Repos (I think it comes with Azure DevOps) or else.

I wrote a simple webapp to auto-detect charset of uploaded text file and convert to UTF-8, no database yet.

Second step, on Azure, create Azure Web App. Choose Java SE on Linux platform (ongoing support for Windows). There is a free tier F1 SKU (it is slow, as expected, but free of charge).

This step could also be done as Java code (infrastructure as code) via Azure CLI or Java (among other options).

Third step, create Azure Pipeline to build and deploy. It connects the code from one’s repository and webapp deployment on Azure. One can enter Azure DevOps from either GitHub Marketplace or Azure.

Create a new Builds Pipeline, select the repository. DevOps would automatically detect the build tool. Choose “Maven package Java project Web App to Linux on Azure” (I would assume similar for Gradle).

Connect with Azure and choose the Web App name created in second step. Review the final YAML configure (it should contains 3 jobs in Build stage, and 1 job in Deploy stage).

And it is done. First build/deploy would be ongoing, and next time code change on master new build/deploy would run automatically.

Code committed to repository would be live in a few minutes.

PS, a few side notes:

  1. tika-core has the interface, but to detect charset, tika-parsers is required.
  2. There is still bugs in Azure Pipeline (like status not updated), hence there is comment trigger.
Posted in Cloud, Computer and Internet | Tagged | Leave a comment

service discovery in kubernetes

Kubernetes utilizes DNS for service discovery. It is quite easy. One can use host name service_name.namespace (in my previous sample, code-sharing-api.default) to access the service (with load-balancing).

I was preparing a sample front-end webapp, and then figured out that it actually required an API gateway (Ingress, in kubernetes), instead of service discovery mechanism. Else I would face the CORS problem (front-end and back-end being on different host or port), unnecessarily.

Posted in Cloud, Computer and Internet | Tagged | Leave a comment

spring boot, docker, kubernetes

In previous post, I’ve started a working Minikube on laptop. But the deployment used a prebuilt Node.js echo server image.

Here I will go through webapp built by Spring Boot, Docker image, and Kubernetes deployment/service configured via YAML.

First, make a simple webapp by Spring Boot. Any app would do. Here I just paste my sample app.

<!--pre class="wp-block-syntaxhighlighter-code brush: groovy; notranslate"-->plugins {
	id 'org.springframework.boot' version '2.1.6.RELEASE'
	id 'java'
	id 'io.freefair.lombok' version '3.8.0'
}

apply plugin: 'io.spring.dependency-management'

group = 'org.weidongxu.app'
version = '1.0.0'
sourceCompatibility = '1.8'

repositories {
	mavenCentral()
}

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	testImplementation 'org.springframework.boot:spring-boot-starter-test'
}<!--/pre-->
<!--pre class="wp-block-syntaxhighlighter-code brush: java; notranslate"-->package org.weidongxu.app.codesharingapi;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;

import javax.annotation.PostConstruct;
import java.time.Instant;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

@SpringBootApplication
@RestController
public class CodeSharingApiApplication {

	public static void main(String[] args) {
		SpringApplication.run(CodeSharingApiApplication.class, args);
	}

	@GetMapping(
			value = "/",
			produces = MediaType.APPLICATION_JSON_VALUE
	)
	public List<CodeBlock&gt; get() {
		List<CodeBlock&gt; blocks = new ArrayList<&gt;(codeBlocks.values());
		blocks.sort((CodeBlock o1, CodeBlock o2) -&gt; Long.compare(o1.getModifiedTsMs(), o2.getModifiedTsMs()));
		return blocks;
	}

	@PostMapping(
			value = "/",
			consumes = MediaType.APPLICATION_JSON_VALUE,
			produces = MediaType.APPLICATION_JSON_VALUE
	)
	public CodeBlock post(@RequestBody CodeBlock codeBlock) {
		codeBlocks.put(codeBlock.getId(), codeBlock);
		return codeBlock;
	}

	@PostConstruct
	private void postConstruct() {
		CodeBlock codeBlock = new CodeBlock("print [0..]");
		codeBlocks.put(codeBlock.getId(), codeBlock);
	}

	private final ConcurrentMap<String, CodeBlock&gt; codeBlocks = new ConcurrentHashMap<&gt;();

	private static class CodeBlock {
		@Getter @Setter private String id;
		@Getter @Setter private String code;
		@Getter @Setter private long version;
		@Getter @Setter private long modifiedTsMs;

		public CodeBlock() {
			this.id = UUID.randomUUID().toString();
			this.version = 1L;
			this.modifiedTsMs = Instant.now().toEpochMilli();
			this.code = "";
		}

		public CodeBlock(String code) {
			this();
			this.code = code;
		}

		public CodeBlock(String id, String code, long version, long modifiedTsMs) {
			this.id = id;
			this.code = code;
			this.version = version;
			this.modifiedTsMs = modifiedTsMs;
		}
	}
}<!--/pre-->

Build it.

./gradlew build

Note Spring Boot uses port 8080 by default (it can be modified in application.properties).

Second, create Docker image for the webapp. Make sure docker command line is installed.

I will reuse docker daemon within Minikube VM. Before creating Docker image, start Minikube and source its docker env.

minikube start --image-mirror-country cn
eval $(minikube docker-env)

Create a Dockerfile for the webapp.

FROM openjdk:8-jre
VOLUME /tmp
COPY build/libs/*.jar app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]

Build Docker image.

docker build -t org.weidongxu.app/code-sharing-api:v1 .

Finally, create deployment and service on Kubernetes. This time I use YAML configuration file.

<!--pre class="wp-block-syntaxhighlighter-code brush: plain; notranslate"-->apiVersion: apps/v1
kind: Deployment
metadata:
  name: code-sharing-api
  labels:
    app: code-sharing-api
spec:
  replicas: 2
  selector:
    matchLabels:
      app: code-sharing-api
  template:
    metadata:
      labels:
        app: code-sharing-api
    spec:
      containers:
      - name: code-sharing-api
        image: org.weidongxu.app/code-sharing-api:v1
        imagePullPolicy: Never
        ports:
        - containerPort: 8080<!--/pre-->

Note that I am using docker daemon inside Minikube VM, hence imagePullPolicy: Never.

<!--pre class="wp-block-syntaxhighlighter-code brush: plain; notranslate"-->apiVersion: v1
kind: Service
metadata:
  name: code-sharing-api
  labels:
    app: code-sharing-api
spec:
  type: NodePort
  selector:
    app: code-sharing-api
  ports:
  - protocol: TCP
    nodePort: 30001
    port: 8080
    targetPort: 8080<!--/pre-->

And call kubecrl to create the deployment and service.

kubectl create -f k8s-deployment.yaml
kubectl create -f k8s-service.yaml
Posted in Cloud, Computer and Internet | Tagged | Leave a comment

taste some kubernetes, start with minikube

I was not a big fan on container technology. Well, duty calls. So I would start with tutorials and Minikube on laptop.

Installation is simple. Instruction is here.

One need kubectl and minikube, both is available as pre-built binary on Mac, and can be readily downloaded via curl.
For simplicity best not to move them to /usr/local/bin as in instruction. Just put them somewhere in user PATH would do.

One more requirement is hypervisor. I would use VirtualBox for Mac (HyperKit need a bit work, while VMWare Fusion cost money).

After installation, one need to start Minikube for the first-time. minikube start would do.

However, for mainland China, one had to retrieve ISO and image from AlibabaCloud mirror. Command line would be

minikube start --iso-url 'https://kubernetes.oss-cn-hangzhou.aliyuncs.com/minikube/iso/minikube-v1.2.0.iso' --image-mirror-country cn

for current Minikube version 1.2.0. There is a GitHub with modified default ISO URL. Here the ISO URL could be found as DefaultISOURL at minikube/pkg/minikube/constants/constants.go
If there is future upgrade, remember to use latest version.

After Minikube is started, one can try with some deployment. Start a Node.js echo server (again, mainland China need to use a mirror, instead of default gcr.io).

kubectl create deployment hello-node --image=registry.cn-hangzhou.aliyuncs.com/google_containers/echoserver:1.10

It will create echo server deployment on the pod. The server can be accessed locally within the pod.

kubectl exec $POD_NAME -- curl localhost:8080

Next, one can expose it as service.

kubectl expose deployment hello-node --type=LoadBalancer --port=8080

Check status.

kubectl get all

Sample response (note the external NODE_PORT on service/hello-node, 30252).

NAME                              READY   STATUS    RESTARTS   AGE
 pod/hello-node-67c8cf8784-9t2h6   1/1     Running   0          15m
 

 

 NAME                 TYPE           CLUSTER-IP     EXTERNAL-IP   PORT(S)          AGE
 service/hello-node   LoadBalancer   10.104.130.7   <pending>     8080:30252/TCP   3m49s
 service/kubernetes   ClusterIP      10.96.0.1      <none>        443/TCP          5h6m
 

 

 NAME                         READY   UP-TO-DATE   AVAILABLE   AGE
 deployment.apps/hello-node   1/1     1            1           16m
 

 NAME                                    DESIRED   CURRENT   READY   AGE
 replicaset.apps/hello-node-67c8cf8784   1         1         1       15m
 

All is fine. Now it can be accessed externally.

minikube service hello-node

Above command line is just a convenience, simple

curl $(minikube ip):$NODE_PORT

would do.

Further, deployment can be scaled-up (to 2 pods).

kubectl scale deployment.apps/hello-node --replicas=2

After having enough of our echo service, one can shut it down.

kubectl delete service hello-node
kubectl delete deployment hello-node

Further, stop Minikube (to save some CPU and memory).

minikube stop
Posted in Cloud, Computer and Internet | Tagged | Leave a comment

Unicode equivalence

A few days ago I planned to write a Python script to migrate some music playlist from Mac to PC. The task was just to parse XML file and convert the content to PPL file (just plain text). How hard can that be? Done and done within an hour.

Well, it works for most playlist files. But a few fails to load in PC.

All failed playlist involved some Japanese characters in file name. Text editor did not find anything suspicious. Hence I had to go raw. “od -bc” helped a lot.

Content of one file got this:

343 202 253 343 202 231
カ  **  **    ゙  **  ** 

Content of another file got this:

343 202 254
ガ  **  ** 

It should be same Japanese character “ガ”. Why does it get 2 different unicode representation?

OK, here is the culprit: Unicode equivalence. Certain code point sequence is canonically equivalent (e.g. é = e + ◌́, ガ = カ + ◌゙). The former is NFC (Normalization Form Canonical Composition) and the latter is NFD (Normalization Form Canonical Decomposition).

If we know certain file system mainly uses NFC, a simple solution is to convert file name reference to NFC as well. Python code is simple:

string = unicodedata.normalize('NFC', string)
Posted in Computer and Internet, Operation System and Linux | Tagged | Leave a comment

The Iliad of Homer

The Project Gutenberg EBook of The Iliad of Homer by Homer

Online course The Ancient Greek Hero and book The Ancient Greek Hero in 24 Hours by Gregory Nagy

“Hear me, and judge, ye sisters of the main!
How just a cause has Thetis to complain!
How wretched, were I mortal, were my fate!
How more than wretched in the immortal state!
Sprung from my bed a godlike hero came,
The bravest far that ever bore the name;
Like some fair olive, by my careful hand
He grew, he flourish’d and adorn’d the land
To Troy I sent him: but the fates ordain
He never, never must return again.
So short a space the light of heaven to view,
So short, alas! and fill’d with anguish too!
Hear how his sorrows echo through the shore!
I cannot ease them, but I must deplore;
I go at least to bear a tender part,
And mourn my loved-one with a mother’s heart.”

Iliad XVIII

“Ah think, thou favour’d of the powers divine!
Think of thy father’s age, and pity mine!
In me that father’s reverend image trace,
Those silver hairs, that venerable face;
His trembling limbs, his helpless person, see!
In all my equal, but in misery!
Yet now, perhaps, some turn of human fate
Expels him helpless from his peaceful state;
Think, from some powerful foe thou seest him fly,
And beg protection with a feeble cry.
Yet still one comfort in his soul may rise;
He hears his son still lives to glad his eyes,
And, hearing, still may hope a better day
May send him thee, to chase that foe away.
No comfort to my griefs, no hopes remain,
The best, the bravest, of my sons are slain!
Yet what a race! ere Greece to Ilion came,
The pledge of many a loved and loving dame:
Nineteen one mother bore—Dead, all are dead!
How oft, alas! has wretched Priam bled!
Still one was left their loss to recompense;
His father’s hope, his country’s last defence.
Him too thy rage has slain! beneath thy steel,
Unhappy in his country’s cause he fell!

“For him through hostile camps I bent my way,
For him thus prostrate at thy feet I lay;
Large gifts proportion’d to thy wrath I bear;
O hear the wretched, and the gods revere!
“Think of thy father, and this face behold!
See him in me, as helpless and as old!
Though not so wretched: there he yields to me,
The first of men in sovereign misery!
Thus forced to kneel, thus grovelling to embrace
The scourge and ruin of my realm and race;
Suppliant my children’s murderer to implore,
And kiss those hands yet reeking with their gore!”

These words soft pity in the chief inspire,
Touch’d with the dear remembrance of his sire.
Then with his hand (as prostrate still he lay)
The old man’s cheek he gently turn’d away.
Now each by turns indulged the gush of woe;
And now the mingled tides together flow:
This low on earth, that gently bending o’er;
A father one, and one a son deplore:
But great Achilles different passions rend,
And now his sire he mourns, and now his friend.
The infectious softness through the heroes ran;
One universal solemn shower began;
They bore as heroes, but they felt as man.

Iliad XXIV
Posted in Book | Leave a comment