一个基于aliyun-oss-python-sdk的代码同步工具,可以指定同步文件的类型。(水平很烂,请轻喷 (:3 」∠) ) 要使用的话需要租一个阿里云oss2云存储服务,很便宜,9rmb/year 因为流量是走的开发者接口,所以流量免费,用来同步一些小文件很划算 我是用来同步代码文件的,当然其他类型的文件也可以。 交互方式采用命令行。 注意: 使用前须将 endpoint accessKeyId accessKeySecret 替换为自己的,以及需要在在oss2控制台上新建一个Bucket 然后将Bucket_name替换为自己的Bucket_name auth=oss2.Auth(accessKeyId,accessKeySecret) bucket=oss2.Bucket(auth,endpoint,Bucket_name)
扫描,更新文件都采用多线程的方式,注意线程不能开太多,会发生网络阻塞,且容易造成云端服务器无响应。
个人水平有限,有问题欢迎来交流。
oss2_sync_tool_v3.py.py
import os,shutil
import oss2
import logging
import my_utils as utils
import threading
#配置信息,注意替换为自己的
endpoint=''
accessKeyId=''
accessKeySecret=''
#local_path='C:/Users/pc/workspace/java workspace/'
local_path_list=['C:/Users/pc/workspace/java workspace/','C:/Users/pc/workspace/sublime/']#因为分割符的位置在下面是硬性指定的,所以最好保证列表中的目录是等深度的,不然就得单独处理,这里就对应45,125行,129,130,137,138行的索引5,深度改变了,索引也要改
temp_path='C:/Users/pc/oss2_sync_cache/'#缓存路径
cloud_path='my_src_code/'#云端文件夹
include_suffix=['c','cpp','java','py','txt','xml','properties','h','hpp']#待同步文件类型可以自行指定,进一步的,也可以对其他的文件信息进行个性化定制,如文件大小等
auth=oss2.Auth(accessKeyId,accessKeySecret)
bucket=oss2.Bucket(auth,endpoint,'xxxx')#这里的xxxx改成自己的bucket_name
src_file_list=[]
temp_file_list=[]
lock=threading.Lock()
def generate_cache(src_file_list,temp_file_list):
for srcEntry,tempEntry in zip(src_file_list,temp_file_list):
if not os.path.exists(tempEntry):#先看文件存在否
lock.acquire()
if not os.path.exists('/'.join(tempEntry.split('/')[:-1])):#再看目录存在否
os.makedirs('/'.join(tempEntry.split('/')[:-1]))
lock.release()
with open(tempEntry,'w+') as f:#先创建空文件(因为不存在)
f.close()
if utils.newer(srcEntry,tempEntry) or os.path.getsize(tempEntry)==0:#如果源文件较新或目标文件为空文件(可能是之前刚创建的),才更新
shutil.copy2(srcEntry,tempEntry)#copy2()会连带着源文件状态(时间戳)一起复制,copy+copystat
#扫描源目录,并生成临时存储信息到temp_file_list中,然后根据temp_file_list来更新临时文件
def src2temp(temp_path,thread_number=16):
#1.扫描源目录,并将符合的文件路径存储到src_file_list中
for entry_list in local_path_list:
utils.scan(entry_list,src_file_list,include_suffix)
for entry in src_file_list:
temp_file_list.append(temp_path+'/'.join(entry.split('/')[5:]))
#print(temp_path+'/'.join(entry.split('/')[5:]))
assert(len(src_file_list)==len(temp_file_list))
#更新临时文件,引入多线程处理
tmp_s=[]
tmp_d=[]
threads=[]
for i in range(thread_number):
tmp_s.append(list())
tmp_d.append(list())
for i in range(len(src_file_list)):
tmp_s[i%thread_number].append(src_file_list[i].replace('//','/'))
tmp_d[i%thread_number].append(temp_file_list[i].replace('//','/'))
for i in range(thread_number):
threads.append(threading.Thread(target=generate_cache,args=(tmp_s[i],tmp_d[i],)))
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join()
def update_file(temp_file_list):
for entry in temp_file_list:
#拆分本地目录名和文件名,生成云端路径
utils.update_file(bucket,
'/'.join(entry.split('/')[:-1])+'/',
entry.split('/')[-1],
(cloud_path+'/'.join(entry.split('/')[4:-1])+'/').replace('//','/'),
#//会导致云端生成空目录,所以替换
entry.split('/')[-1])
#print('/'.join(entry.split('/')[:-1])+'/'+entry.split('/')[-1])
#print((cloud_path+'/'.join(entry.split('/')[5:-1])+'/').replace('//','/')+entry.split('/')[-1])
#将临时文件同步更新到云端,不直接从源文件同步到云端,做了一个隔离,对源文件只有读权限,对临时文件才有读写权限
def temp2cloud(endpoint,accessKeyId,accessKeySecret,thread_number=16):
#扫描源目录,并生成临时存储信息到temp_file_list中,然后根据temp_file_list来更新临时文件
src2temp(temp_path)
if not bucket.object_exists(cloud_path):
bucket.put_object(cloud_path)
tmp_c=[]
threads=[]
total_size=0
for i in range(thread_number):
tmp_c.append(list())
for i in range(len(temp_file_list)):
tmp_c[i%thread_number].append(temp_file_list[i].replace('//','/'))
total_size+=os.path.getsize(temp_file_list[i].replace('//','/'))
for i in range(thread_number):
threads.append(threading.Thread(target=update_file,args=(tmp_c[i],)))#特别注意要有逗号
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join()
print('scan file number: {0}\ntotal size: {0} kB'.format(len(temp_file_list),total_size/1000))
def print_info():
print('*************** oss2_sync_tool_v3 **********************')
print('* input ls: list the files that need to be updated. *')
print('* input ls -u: update list after execute ls. *')
print('* input update: update all files. *')
print('* input cfg-s: configure include_suffix. *')
print('* input cfg-n: configure oss2-bucket-name. *')
print('* input restore: restore to the original state. *')
print('* input clear: clear all temp files. *')
print('* input show-info: set up display information or not. *')
print('* input help: show the help information. *')
print('* input q: exit the program. *')
print('*************************************** --xycode *******')
ls_update_list=[]#待更新列表
def ls_part(ls_list):
for entry in ls_list:#ls_list相当于之前的src_file_list,所以下面是[5:]
temp_cloud_path=(cloud_path+'/'.join(entry.split('/')[5:-1])+'/').replace('//','/')+entry.split('/')[-1]
#print(temp_cloud_path)
if not bucket.object_exists(temp_cloud_path):#云端不存在的话
print(entry)
generate_cache([entry],[temp_path+'/'.join(entry.split('/')[5:])])#先生成缓存
ls_update_list.append(temp_path+'/'.join(entry.split('/')[5:]))
continue
statinfo=os.stat(entry)
local_last_modified_time=int(statinfo.st_mtime)-28800#统一转换为以秒为单位,北京时间-8小时=GMT
cloud_last_modified_time=utils.date_to_num(bucket.get_object(temp_cloud_path))
if local_last_modified_time>cloud_last_modified_time:#本地文件较新
print(entry)
shutil.copy2(entry,temp_path+'/'.join(entry.split('/')[5:]))
ls_update_list.append(temp_path+'/'.join(entry.split('/')[5:]))
def ls(thread_number=24):
ls_list=[]
for entry_list in local_path_list:
utils.scan(entry_list,ls_list,include_suffix)
tmp_l=[]
threads=[]
for i in range(thread_number):
tmp_l.append(list())
for i in range(len(ls_list)):#分配任务
tmp_l[i%thread_number].append(ls_list[i].replace('//','/'))
for i in range(thread_number):
threads.append(threading.Thread(target=ls_part,args=(tmp_l[i],)))#特别注意要有逗号
for t in threads:
t.setDaemon(True)
t.start()
for t in threads:
t.join()
def clear():
str=input('clear all temp file?(y/n)\n')
if str in ['Y','y']:
if os.path.exists(temp_path):
shutil.rmtree(temp_path)
os.makedirs(temp_path)
else:
return
store_include_suffix=[]
def cfg_suffix():
global include_suffix #这句必须有,否则报错
global store_include_suffix
store_include_suffix=include_suffix.copy()
include_suffix.clear()
s=input('please input suffix-name,separated by space:\n')
include_suffix=s.split(' ')
'''
以后可以考虑用命令行设置
endpoint
accessKeyId
accessKeySecret
指定bucket name
以及用命令行恢复
这里只是恢复include_suffix
'''
def restore():
global include_suffix#注意
global store_include_suffix#注意
include_suffix=store_include_suffix.copy()
def interact(show_info=True):
if show_info: print_info()
command=input('please input a legal commmad:\n')
if command=='ls':
ls()
if show_info:
interact(True)
else:
interact(False)
elif command=='ls -u':
if ls_update_list==[]:
print("ls_update_list is empty,no need to update.")
print("please check for updates to execute command \'ls\' firstly.")
else:
update_file(ls_update_list)
ls_update_list.clear()
if show_info:
interact(True)
else:
interact(False)
elif command=='cfg-s':
cfg_suffix()
if show_info:
interact(True)
else:
interact(False)
elif command=='cfg-n':
global bucket
oss2_bucket_name=input('please input oss2-bucket-name:\n(legal name: [\'xycode1\',\'xycode2\'])\n')
if oss2_bucket_name in ['xycode1','xycode2']:
bucket=oss2.Bucket(auth,endpoint,oss2_bucket_name)
else:
print('illegal oss2-bucket-name!')
if show_info:
interact(True)
else:
interact(False)
elif command=='restore':
restore()
if show_info:
interact(True)
else:
interact(False)
elif command=='update':
src_file_list.clear()
temp_file_list.clear()
if not os.path.exists(temp_path):
os.makedirs(temp_path)
#src2temp(temp_path)
temp2cloud(endpoint,accessKeyId,accessKeySecret)
if show_info:
interact(True)
else:
interact(False)
elif command=='clear':
clear()
if show_info:
interact(True)
else:
interact(False)
elif command=='help':
print_info()
if show_info:
interact(True)
else:
interact(False)
elif command=='show-info':
s=input('show?(y/n)\n')
if s in ['Y','y']:
interact(True)
else:
interact(False)
elif command=='q':
return
else:
print('incorrect command,please input again.')
if show_info:
interact(True)
else:
interact(False)
if __name__ == '__main__':
# 设置日志等级为CRITICAL
log_file_path = "log.log"
oss2.set_file_logger(log_file_path, 'oss2', logging.CRITICAL)
interact()
my_utils.py
import os,shutil,re
import oss2
import time
def date_to_num(GetObjectResult):
result=' '.join(GetObjectResult.headers['Last-Modified'].split(' ')[1:-1])
date_result=time.strptime(result,'%d %b %Y %H:%M:%S')
date_num=time.mktime(date_result)
return int(date_num)
#将本地文件同步到云端
def update_file(bucket,local_file_path,local_filename,cloud_file_path,cloud_filename):
statinfo=os.stat(local_file_path+local_filename)
#注意时区的转换
# local_last_modified_time=time.mktime(time.localtime(statinfo.st_mtime))-28800#统一转换为以秒为单位,北京时间-8小时=GMT
local_last_modified_time=int(statinfo.st_mtime)-28800#统一转换为以秒为单位,北京时间-8小时=GMT
with open(oss2.to_unicode(local_file_path+local_filename),'rb') as f:
if bucket.object_exists(cloud_file_path+cloud_filename):#云端已经存在该文件
cloud_last_modified_time=date_to_num(bucket.get_object(cloud_file_path+cloud_filename))
# print(local_last_modified_time,cloud_last_modified_time)
if local_last_modified_time>cloud_last_modified_time:
#如果本地的文件较新才更新到云端
print('update '+local_filename+'.')
bucket.put_object(cloud_file_path+cloud_filename,f)#将本地文件更新到云端cloud_file_path+cloud_filename
else:
print(cloud_filename+' already up-to-date.')
else:
print(cloud_filename+' does not exist,start upload...')
bucket.put_object(cloud_file_path+cloud_filename,f)#将本地文件上传到云端cloud_file_path+cloud_filename
#判断源文件与临时文件哪个较新
def newer(srcEntry,destEntry):
statinfo=os.stat(srcEntry)
src_last_modified_time=int(statinfo.st_mtime)
statinfo=os.stat(destEntry)
dest_last_modified_time=int(statinfo.st_mtime)
return src_last_modified_time>dest_last_modified_time
#扫描目录的函数,并将符合的文件路径存储到src_file_list中
def scan(path,src_file_list,include_suffix):
if not os.path.exists(path):
return
if os.path.isdir(path):
for entry in os.listdir(path):
scan(path+'/'+entry,src_file_list,include_suffix)
else:
if path.split('.')[-1] in include_suffix:
src_file_list.append(path)
#print(path)
详见我的github主页:GitHub - xycodec/oss2_sync_tool_v4: 一个基于aliyun-oss-python-sdk的同步工具