目录
背景
内容
准备工作
在windows平台上
在linux平台上
正式工作
生成差分文件思路
作用差分文件思路
在保持相同目录结构进行差分增量升级
服务端(生成差分文件)
客户端(作用差分文件)
12月6日更新(修复bug)
执行分析
注意事项和关注的地方:
背景
像常见的Android 的linux平台,游戏,系统更新都会用到这一种方式。
以我自己的理解,这种方式有些像git中的版本管理, 以最少的时间进行版本管理.核心在于如何去记录文件的差异.
服务器端:
通过
bsdiff old new patchfile_path
生成差分文件.一般以.patch的文件命名.
客户端: 根据patch文件 通过
bspatch oldfile newfile patchfile_path
一般情况下,本以为可以直接通过压缩包的形式去进行, 安卓平台的.apk文件是可以的,单片机的可执行hex等格式的文件也是可以的. 但通过压缩的压缩包则可能会有隐患. 通过开会讨论以及本人查询资料发现 会因为压缩算法,压缩文件顺序的不一样而导致差分包出现问题.
原因有
主要原因有:
1. 不同的压缩算法会产生不同的压缩数据。即使原始数据相同,通过不同算法压缩结果也不完全一样。这会直接影响bsdiff的比较结果。
2. 即使使用同一压缩算法,压缩文件内原始数据的顺序改变也可能改变压缩效果。压缩算法利用重复模式来达到压缩效果。顺序改变会打乱这种模式。
3. bsdiff是按顺序比较数据生成差分的。所以就算压缩原理数据相同,其在压缩文件中的顺序变化也会导致bsdiff生成不同的差分补丁。
4. 压缩算法本身就利用了字典及顺序来提高压缩率。这与bsdiff的工作原理有一定冲突。综上,为了生成一致的bsdiff补丁,同一个数据生成压缩包时需要保证使用同一算法和稳定的顺序。否则差分结果可能会有较大变化。一般需要压缩数据再差分时,需要注意控制这两个因素,或者考虑在解压后对原始数据文件差分。
所以,考虑解压后保持相同的目录结构进行差分,即为生成的.patch文件和原工程有相同的目录.
所以需要写一个脚本,生成一个差分文件夹(目录),这个差分文件夹与原工程有相同的目录结构.
后面再根据这个差分文件夹进行升级,即为patch文件与原文件作用生成新文件,新目标和原目标相同.通过这种服务器上生成差分包,客户端上作用差分包的形式,差分包可以压缩,在客户端上解压缩,这样能更快更合理.
所以,总共需要有2个bash脚本,一个放服务器上,生成差分包.一个放客户端上,在收到差分包后进行本地升级.
内容
bsdiff和bspatch去官网上截至2023年10月27日没有下载源码的权限,所以得去别的地方找找源码.
准备工作
在windows平台上
参考
whistle713/bsdiff-win: bsdiff Windows binaries and Visual Studio 2015/2019 project. (github.com)
里面有提供能够在windows平台上允许的.exe可执行文件.
在linux平台上
参考
红橙Darren视频笔记 bsdiff bspatch 使用(Linux下)_洌冰的博客-CSDN博客
完成编译
正式工作
这里需要考虑到旧的目标和新的目标的一些特殊情况.
- 新目标有新增文件的情况
- 新目标有删除原来旧文件的情况
- 新目标和旧目标的目录和文件都能对上,只是有变化.
- 旧目标和新目标有 大小为0 bytes 文件的情况(bsdiff失效)
相信一般的升级都会遇到 1.2.3.4所有情况,
对于第4种情况,不清楚是不是bsdiff的版本问题还是linux系统的问题,我在本地的liunx没有这个问题.
bsdiff在处理 大小为 0 bytes的文件时在linux上报错
报错
bsdiff:mmap() xxx:Invalid argument
思路:
对于第一种和第二种情况.
新目标新增: 在旧目标中生成一个相同名字的文件,不过大小为0 bytes
新目标有删除有原来旧文件的情况: 在新目标中生成一个相同名字的文件,大小依然为0bytes
这样的话,只要不出现 4 的这种问题都是能够通过bsdiff 生成相应的bspatch文件的.
生成差分文件思路
1.同步旧目标(对应新目标有文件增加时)
2.同步新目标(对应新目标删除了旧文件时)
3.递归遍历目标中的每一个文件,在另一个目标中进行查找, 可以直接通过bsdiiff 生成差分文件,
即使是两个相同的文件,也会生成patch文件,只不过bspatch 作用这个patch文件时并不会起作用,这样是非常方便了,都不需要进行判断了。这样表现为每一个文件都有对应的差分文件.(这个需要再我的代码上改一改)
而我下面并没有这么做,而是根据md5的值判断文件不同后再生成对应的patch文件.
作用差分文件思路
直接遍历生成的差分文件目录结构,调用bspatch.
在保持相同目录结构进行差分增量升级
服务端(生成差分文件)
调用.
./gen.sh(脚本名) ./old(旧目录) ./new (新目录)
最终会生成一个以日期后缀的差分文件的目录(和原目录保持相同的目录结构)
#!/bin/bash
# check if two arguments are given
if [ $# -ne 2 ]; then
echo "Usage: $0 oldfolder newfolder"
exit 1
fi
# check if the arguments are valid directories
if [ ! -d "$1" ] || [ ! -d "$2" ]; then
echo "Invalid directories"
exit 2
fi
# if it is ended by '/' delete '/'
inputemp=$1
if test "${inputemp: -1}" == "/";then
s1=${inputemp%/}
else
s1=$inputemp
fi
inputemp=$2
if test "${inputemp: -1}" == "/";then
s2=${inputemp%/}
else
s2=$inputemp
fi
# create a new directory for patch files
patch_dir="patch_$(date +%Y%m%d%H%M%S)"
mkdir -p "$patch_dir"
# sync in new target
find "$s1" -type f | while read oldfile; do
# get the relative path of the file
rel_path=${oldfile#$s1/}
# get the corresponding file in the second directory
newfile="$s2/$rel_path"
# exist in old and not exist in new and create same name to instead in the new folder
if [ ! -f "$newfile" ]; then
echo -e "\033[0;36m [disapper in new]: $newfile Generate 0 Bytes to instead in new target \033[0m"
mkdir -p "$(dirname $newfile)"
> $newfile
fi
done
# sync in old target
find "$s2" -type f | while read newfile; do
# get the relative path of the file
rel_path=${newfile#$s2/}
# get the corresponding file in the second directory
oldfile="$s1/$rel_path"
# exist in new and not exist in old and create same name to instead in the old folder
if [ ! -f "$oldfile" ]; then
echo -e "\033[0;36m [disapper in old]: $oldfile Generate 0 Bytes to instead in old target \033[0m"
# create the parent directory if needed
mkdir -p "$(dirname oldfile)"
> $oldfile
fi
done
# Generate patch
find "$s1" -type f | while read oldfile; do
# get the relative path of the file
rel_path=${oldfile#$s1/}
# get the corresponding file in the second directory
newfile="$s2/$rel_path"
# Haved sync and create the patch file name
patch_file="$patch_dir/$rel_path.patch"
# create the parent directory if needed
mkdir -p "$(dirname "$patch_file")"
# use bsdiff to generate the patch file
oldmd5=$(md5sum $oldfile | awk '{print $1}')
newmd5=$(md5sum $newfile | awk '{print $1}')
if [ "$oldmd5" = "$newmd5" ]; then
echo -e "\033[0;32m Don't Need to Change \033[0m"
else
bsdiff "$oldfile" "$newfile" "$patch_file"
echo -e "\033[0;33mGenerated patch for $rel_path \033[0m"
fi
done
echo "Done. Patch files are in $patch_dir"
客户端(作用差分文件)
调用
脚本名 旧目标 新目标(也可以是旧目标 ,相当与替换旧目标) 差分目录
#!/bin/bash
# check if two arguments are given
if [ $# -ne 3 ]; then
echo "Usage: cmd oldfolder newfolder patchfolders"
exit 1
fi
# new generate
if [ ! -e "$2" ]; then
mkdir $2
fi
# check if the arguments are valid directories
if [ ! -d "$1" ] || [ ! -d "$3" ] ; then
echo "Invalid directories"
exit 2
fi
# if it is ended by '/' delete '/'
inputemp=$1
if test "${inputemp: -1}" == "/";then
s1=${inputemp%/}
else
s1=$inputemp
fi
inputemp=$2
if test "${inputemp: -1}" == "/";then
s2=${inputemp%/}
else
s2=$inputemp
fi
inputemp=$3
if test "${inputemp: -1}" == "/";then
s3=${inputemp%/}
else
s3=$inputemp
fi
#loop item in path_item
find "$s3" -type f -name "*.patch" | while read patch_item; do
temp=${patch_item#$s3/}
temp=${temp%.patch} #equal to temp=${temp:0:${#temp}-6}
oldfile="$s1/$temp"
newfile="$s2/$temp"
mkdir -p "$(dirname "$newfile")"
echo -e "\033[0;32m Generate $oldfile $newfile \033[0m"
# execute bspatch
bspatch "$oldfile" "$newfile" "$patch_item"
done
如何使用?
一般调用过程
diff -rq ./old ./new(此时会看到文件差异)
./gen ./old ./new
./upgrate ./old ./old ./patch_xxx
diff -rq ./old ./new (没有输出表示更新升级完毕)
12月6日更新(修复bug)
出现bug,找不到软链接文件,差分升级无法作用在存在的软连接文件.
于是更新.
- 解决找不到软连接文件的问题,现在能找到所有文件了(包括软链接和隐藏文件)
- 删除同步过程中的空目录和空文件(0字节文件)
gen.sh
#!/bin/bash
#record waiting_for_delete_index
wddi="waiting_for_dindex"
GetAllfile()
{
local ret=()
local links=$(find "$1" -type l)
local normal_files=$(find "$1" -type f)
while read -r item
do
ret+=($item)
done <<< "$links"
while read -r item
do
ret+=($item)
done <<< "$normal_files"
echo ${ret[@]}
}
# check if two arguments are given
if [ $# -ne 2 ]; then
echo "Usage: $0 oldfolder newfolder"
exit 1
fi
# check if the arguments are valid directories
if [ ! -d "$1" ] || [ ! -d "$2" ]; then
echo "Invalid directories"
exit 2
fi
# if it is ended by '/' delete '/'
inputemp=$1
if test "${inputemp: -1}" == "/";then
s1=${inputemp%/}
else
s1=$inputemp
fi
inputemp=$2
if test "${inputemp: -1}" == "/";then
s2=${inputemp%/}
else
s2=$inputemp
fi
# create a new directory for patch files
patch_dir="$(dirname $s1)"/patch_"$(echo "$s1" | awk -F'/' '{print $NF}')"
echo patch_path: $patch_dir
mkdir -p "$patch_dir"
wddi="$(dirname $s1)"/"$wddi"
if [ ! -f $wddi ];then
touch "$wddi"
else
> $wddi
fi
# sync in new target
all_files=`GetAllfile $s1`
all_files=$(echo -n "$all_files" | tr ' ' '\n')
echo "$all_files" | while read oldfile; do
# get the relative path of the file
rel_path=${oldfile#$s1/}
# get the corresponding file in the second directory
newfile="$s2/$rel_path"
# exist in old and not exist in new and create same name to instead in the new folder
if [ ! -f "$newfile" ]; then
echo -e "\033[0;36m [disapper in new]: $newfile Generate 0 Bytes to instead in new target \033[0m"
#appends not exist path recorded list
if [ ! -d "$(dirname $newfile)" ];then
mkdir -p "$(dirname $newfile)"
echo "$(dirname $rel_path)" >> $wddi
fi
#write 0 bytes
> $newfile
fi
done
# sync in old target
all_files=`GetAllfile $s2`
all_files=$(echo -n "$all_files" | tr ' ' '\n')
echo "$all_files" | while read newfile; do
# get the relative path of the file
rel_path=${newfile#$s2/}
# get the corresponding file in the second directory
oldfile="$s1/$rel_path"
# exist in new and not exist in old and create same name to instead in the old folder
if [ ! -f "$oldfile" ]; then
echo -e "\033[0;36m [disapper in old]: $oldfile Generate 0 Bytes to instead in old target \033[0m"
# create the parent directory if needed
mkdir -p "$(dirname $oldfile)"
> $oldfile
fi
done
# Generate patch
# when running in this poision ,old target is same as new target
all_files=`GetAllfile $s1`
all_files=$(echo -n $all_files | tr ' ' '\n')
echo "$all_files" | while read oldfile; do
# get the relative path of the file
rel_path=${oldfile#$s1/}
# get the corresponding file in the second directory
newfile="$s2/$rel_path"
# Haved sync and create the patch file name
patch_file="$patch_dir/$rel_path.patch"
# create the parent directory if needed
mkdir -p "$(dirname "$patch_file")"
# use bsdiff to generate the patch file
oldmd5=$(md5sum $oldfile | awk '{print $1}')
newmd5=$(md5sum $newfile | awk '{print $1}')
if [ "$oldmd5" = "$newmd5" ]; then
echo -e "\033[0;36m Don't Need to Change \033[0m"
else
bsdiff "$oldfile" "$newfile" "$patch_file"
echo -e "\033[0;36mGenerated patch for $rel_path \033[0m"
fi
done
# after generating patchs_files ,delete extra 0 bytes files by delete index
if [ ! -f $wddi ];
then
exit 1
fi
cat $wddi | while read del_item;
do
realpath=$s2/$del_item
if [ -d "$realpath" ] ;then
echo $realpath
rm -rf $realpath
fi
done
echo "Done. Patch files are in $patch_dir"
upgrate.sh
#!/bin/bash
# wdfi="waiting_for_delete"
wddi="waiting_for_dindex"
# check if two arguments are given
if [ $# -ne 2 ]; then
echo "Usage: upgrade oldfolder patch_path "
echo "example ./upgrade ./test/old/ ./test/boot/"
exit 1
fi
# check if the arguments are valid directories
if [ ! -d "$1" -o ! -d "$2" ] ; then
echo "Invalid directories"
exit 2
fi
# if it is ended by '/' delete '/' => old folder
inputemp=$1
if test "${inputemp: -1}" == "/";then
s1=${inputemp%/}
else
s1=$inputemp
fi
# if it is ended by '/' delete '/'=> patch_path
inputemp=$2
if test "${inputemp: -1}" == "/";then
patch_path=${inputemp%/}
else
patch_path=$inputemp
fi
wddi="$patch_path/$wddi"
echo wddi : "$wddi"
patch_path="$patch_path"/patch_"$(echo "$s1" | awk -F'/' '{print $NF}')"
echo find patch_path: $patch_path
#loop item in path_item
find "$patch_path" -type f -name "*.patch" | while read patch_item; do
temp=${patch_item#$patch_path/}
temp=${temp%.patch} #equal to temp=${temp:0:${#temp}-6}
oldfile="$s1/$temp"
newfile="$s1/$temp"
mkdir -p "$(dirname "$newfile")"
if [ ! -f "$newfile" ];
then
> $newfile
fi
echo -e "\033[0;36m Generate $oldfile $newfile \033[0m"
# execute bspatch
bspatch "$oldfile" "$newfile" "$patch_item"
done
#delete not exist index when processing to delete
if [ ! -f $wddi ];
then
exit 1
fi
cat $wddi | while read del_item;
do
realpath=$s1/$del_item
if [ -d "$realpath" ] ;then
echo $realpath
rm -rf $realpath
fi
done
echo "done"
执行分析
这样在服务端上执行
./gen ./test/old ./test/new (old是旧版本,new为新版本),则在test下生成patch_old,和waiting_for_dindex.文章来源:https://www.toymoban.com/news/detail-730181.html
./upgrade 旧文件位置 pathch文件父路径(不包括pathch文件名)文章来源地址https://www.toymoban.com/news/detail-730181.html
注意事项和关注的地方:
- 在生成patch文件的时候,也就是 ./gen 脚本 ,先让旧目录和新目录进行了同步,最终根据每一个文件生成patch文件,并把这些patch文件单独放在pathch_旧目录名 这个目录下,这个目录和旧目录(新目录有相同的目录结构.
- 在./gen脚本中,因为做了同步,如果存在新版本中删除旧目录,则会将被删除的旧目录同步到新目录中,并把被删除的旧目录存放在一个清单列表中(如wddi),最终在打完patch后会在服务器端删除这些同步的内容.
- ./upgrade脚本 接受传入一个旧目录,和一个patch目录的父路径(不包括patch目录名),这样会在patch的父目录中寻找以旧目录后缀结尾的patch目录,然后遍历这个patch目录依次调用bspatch升级, 最终在客户端调用升级完成后会根据清单去删除同步过程中产生旧目录,0字节的文件.
到了这里,关于Bsdiff,Bspatch 的差分增量升级(基于Win和Linux)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!